1
1
/*
2
- Copyright 2021 Splunk Inc.
2
+ Copyright 2023 Splunk Inc.
3
3
4
4
Licensed under the Apache License, Version 2.0 (the "License");
5
5
you may not use this file except in compliance with the License.
@@ -31,13 +31,10 @@ func addLinkToSpan(span: Span, valStr: String) {
31
31
span. setAttribute ( key: " link.spanId " , value: spanId)
32
32
}
33
33
34
- func endHttpSpan( span: Span ? , task: URLSessionTask ) {
35
- if span == nil {
36
- return
37
- }
34
+ func endHttpSpan( span: Span , task: URLSessionTask ) {
38
35
let hr : HTTPURLResponse ? = task. response as? HTTPURLResponse
39
36
if hr != nil {
40
- span! . setAttribute ( key: " http.status_code " , value: hr!. statusCode)
37
+ span. setAttribute ( key: " http.status_code " , value: hr!. statusCode)
41
38
// Blerg, looks like an iteration here since it is case sensitive and the case insensitive search assumes single value
42
39
for (key, val) in hr!. allHeaderFields {
43
40
let keyStr = key as? String
@@ -46,27 +43,31 @@ func endHttpSpan(span: Span?, task: URLSessionTask) {
46
43
let valStr = val as? String
47
44
if valStr != nil {
48
45
if valStr!. starts ( with: " traceparent " ) {
49
- addLinkToSpan ( span: span! , valStr: valStr!)
46
+ addLinkToSpan ( span: span, valStr: valStr!)
50
47
}
51
48
}
52
49
}
53
50
}
54
51
}
55
52
}
56
53
if task. error != nil {
57
- span! . setAttribute ( key: " error " , value: true )
58
- span! . setAttribute ( key: " exception.message " , value: task. error!. localizedDescription)
59
- span! . setAttribute ( key: " exception.type " , value: String ( describing: type ( of: task. error!) ) )
54
+ span. setAttribute ( key: " error " , value: true )
55
+ span. setAttribute ( key: " exception.message " , value: task. error!. localizedDescription)
56
+ span. setAttribute ( key: " exception.type " , value: String ( describing: type ( of: task. error!) ) )
60
57
}
61
- span! . setAttribute ( key: " http.response_content_length_uncompressed " , value: Int ( task. countOfBytesReceived) )
58
+ span. setAttribute ( key: " http.response_content_length_uncompressed " , value: Int ( task. countOfBytesReceived) )
62
59
if task. countOfBytesSent != 0 {
63
- span! . setAttribute ( key: " http.request_content_length " , value: Int ( task. countOfBytesSent) )
60
+ span. setAttribute ( key: " http.request_content_length " , value: Int ( task. countOfBytesSent) )
64
61
}
65
- span!. end ( )
62
+ span. end ( )
63
+ }
64
+
65
+ func isSupportedTask( task: URLSessionTask ) -> Bool {
66
+ return task is URLSessionDataTask || task is URLSessionDownloadTask || task is URLSessionUploadTask
66
67
}
67
68
68
69
func startHttpSpan( request: URLRequest ? ) -> Span ? {
69
- if request == nil || request ? . url == nil {
70
+ if request? . url == nil {
70
71
return nil
71
72
}
72
73
let url = request!. url!
@@ -120,117 +121,60 @@ func startHttpSpan(request: URLRequest?) -> Span? {
120
121
return span
121
122
}
122
123
123
- class SessionTaskObserver : NSObject {
124
- var span : Span ?
125
- // Observers aren't kept alive by observing...
126
- var extraRefToSelf : SessionTaskObserver ?
127
- var lock : NSLock = NSLock ( )
128
- override init ( ) {
129
- super. init ( )
130
- extraRefToSelf = self
131
- }
124
+ fileprivate var ASSOC_KEY_SPAN : UInt8 = 0
132
125
133
- override func observeValue( forKeyPath keyPath: String ? , of object: Any ? , change: [ NSKeyValueChangeKey : Any ] ? , context: UnsafeMutableRawPointer ? ) {
134
- lock. lock ( )
126
+ // swiftlint:disable missing_docs
127
+ extension URLSessionTask {
128
+ @objc open func splunk_swizzled_setState( state: URLSessionTask . State ) {
135
129
defer {
136
- lock . unlock ( )
130
+ splunk_swizzled_setState ( state : state )
137
131
}
138
- let task = object as? URLSessionTask
139
- if task == nil {
132
+
133
+ if !isSupportedTask ( task: self ) {
140
134
return
141
135
}
142
- if span == nil {
143
- span = startHttpSpan ( request: task!. originalRequest)
136
+
137
+ if state == URLSessionTask . State. running {
138
+ return
144
139
}
145
- // FIXME possibly also allow .canceling to close the span?
146
- if task!. state == . completed && extraRefToSelf != nil {
147
- endHttpSpan ( span: span,
148
- task: task!)
149
- task!. removeObserver ( self , forKeyPath: " state " )
150
- extraRefToSelf = nil
140
+
141
+ if currentRequest? . url == nil {
142
+ return
151
143
}
152
- }
153
- }
154
144
155
- func wireUpTaskObserver( task: URLSessionTask ) {
156
- task. addObserver ( SessionTaskObserver ( ) , forKeyPath: " state " , options: . new, context: nil )
157
- }
145
+ let maybeSpan : Span ? = objc_getAssociatedObject ( self , & ASSOC_KEY_SPAN) as? Span
158
146
159
- // swiftlint:disable missing_docs
160
- extension URLSession {
161
- @objc open func splunk_swizzled_dataTask( with url: NSURL , completionHandler: @escaping ( Data ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDataTask {
162
- let answer = splunk_swizzled_dataTask ( with: url, completionHandler: completionHandler)
163
- wireUpTaskObserver ( task: answer)
164
- return answer
165
- }
166
-
167
- @objc open func splunk_swizzled_dataTask( with url: NSURL ) -> URLSessionDataTask {
168
- let answer = splunk_swizzled_dataTask ( with: url)
169
- wireUpTaskObserver ( task: answer)
170
- return answer
171
- }
172
-
173
- // rename objc view of func to allow "overloading"
174
- @objc ( splunkSwizzledDataTaskWithRequest: completionHandler: ) open func splunk_swizzled_dataTask( with request: URLRequest , completionHandler: ( ( Data ? , URLResponse ? , Error ? ) -> Void ) ? ) -> URLSessionDataTask {
175
- let answer = splunk_swizzled_dataTask ( with: request, completionHandler: completionHandler)
176
- wireUpTaskObserver ( task: answer)
177
- return answer
178
- }
179
-
180
- @objc ( splunkSwizzledDataTaskWithRequest: ) open func splunk_swizzled_dataTask( with request: URLRequest ) -> URLSessionDataTask {
181
- let answer = splunk_swizzled_dataTask ( with: request)
182
- wireUpTaskObserver ( task: answer)
183
- return answer
184
- }
185
-
186
- // uploads
187
- @objc open func splunk_swizzled_uploadTask( with: URLRequest , from: Data ) -> URLSessionUploadTask {
188
- let answer = splunk_swizzled_uploadTask ( with: with, from: from)
189
- wireUpTaskObserver ( task: answer)
190
- return answer
191
- }
192
- @objc open func splunk_swizzled_uploadTask( with: URLRequest , from: Data , completionHandler: @escaping ( Data ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionUploadTask {
193
- let answer = splunk_swizzled_uploadTask ( with: with, from: from, completionHandler: completionHandler)
194
- wireUpTaskObserver ( task: answer)
195
- return answer
196
- }
197
- @objc open func splunk_swizzled_uploadTask( with: URLRequest , fromFile: NSURL ) -> URLSessionUploadTask {
198
- let answer = splunk_swizzled_uploadTask ( with: with, fromFile: fromFile)
199
- wireUpTaskObserver ( task: answer)
200
- return answer
201
- }
202
- @objc open func splunk_swizzled_uploadTask( with: URLRequest , fromFile: NSURL , completionHandler: @escaping ( Data ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionUploadTask {
203
- let answer = splunk_swizzled_uploadTask ( with: with, fromFile: fromFile, completionHandler: completionHandler)
204
- wireUpTaskObserver ( task: answer)
205
- return answer
206
- }
207
- @objc open func splunk_swizzled_uploadTask( withStreamedRequest: URLRequest ) -> URLSessionUploadTask {
208
- let answer = splunk_swizzled_uploadTask ( withStreamedRequest: withStreamedRequest)
209
- wireUpTaskObserver ( task: answer)
210
- return answer
147
+ if maybeSpan == nil {
148
+ return
149
+ }
150
+
151
+ endHttpSpan ( span: maybeSpan!, task: self )
211
152
}
212
- // download tasks
213
- @objc open func splunk_swizzled_downloadTask( with url: NSURL ) -> URLSessionDownloadTask {
214
- let answer = splunk_swizzled_downloadTask ( with: url)
215
- wireUpTaskObserver ( task: answer)
216
- return answer
153
+
154
+ @objc open func splunk_swizzled_resume( ) {
155
+ defer {
156
+ splunk_swizzled_resume ( )
157
+ }
158
+
159
+ if !isSupportedTask( task: self ) {
160
+ return
161
+ }
162
+
163
+ if self . state == URLSessionTask . State. completed ||
164
+ self . state == URLSessionTask . State. canceling {
165
+ return
166
+ }
167
+
168
+ let existingSpan : Span ? = objc_getAssociatedObject ( self , & ASSOC_KEY_SPAN) as? Span
169
+
170
+ if existingSpan != nil {
171
+ return
172
+ }
173
+
174
+ startHttpSpan ( request: currentRequest) . map { span in
175
+ objc_setAssociatedObject ( self , & ASSOC_KEY_SPAN, span, objc_AssociationPolicy. OBJC_ASSOCIATION_RETAIN)
176
+ }
217
177
}
218
- @objc open func splunk_swizzled_downloadTask( with url: NSURL , completionHandler: @escaping ( URL ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDownloadTask {
219
- let answer = splunk_swizzled_downloadTask ( with: url, completionHandler: completionHandler)
220
- wireUpTaskObserver ( task: answer)
221
- return answer
222
- }
223
- @objc ( splunkSwizzledDownloadTaskWithRequest: completionHandler: ) open func splunk_swizzled_downloadTask( with request: URLRequest , completionHandler: ( ( URL ? , URLResponse ? , Error ? ) -> Void ) ? ) -> URLSessionDownloadTask {
224
- let answer = splunk_swizzled_downloadTask ( with: request, completionHandler: completionHandler)
225
- wireUpTaskObserver ( task: answer)
226
- return answer
227
- }
228
-
229
- @objc ( splunkSwizzledDownloadTaskWithRequest: ) open func splunk_swizzled_downloadTask( with request: URLRequest ) -> URLSessionDataTask {
230
- let answer = splunk_swizzled_downloadTask ( with: request)
231
- wireUpTaskObserver ( task: answer)
232
- return answer
233
- }
234
178
}
235
179
236
180
// FIXME use setImplementation and capture, rather than exchangeImpl
@@ -244,57 +188,57 @@ func swizzle(clazz: AnyClass, orig: Selector, swizzled: Selector) {
244
188
}
245
189
}
246
190
247
- func initalizeNetworkInstrumentation( ) {
248
- let urlsession = URLSession . self
249
-
250
- // This syntax is obnoxious to differentiate with:request from with:url
251
- swizzle ( clazz: urlsession,
252
- orig: #selector( URLSession . dataTask ( with: completionHandler: ) as ( URLSession ) -> ( URL , @escaping ( Data ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDataTask ) ,
253
- swizzled: #selector( URLSession . splunk_swizzled_dataTask ( with: completionHandler: ) as ( URLSession ) -> ( NSURL , @escaping ( Data ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDataTask ) )
254
-
255
- swizzle ( clazz: urlsession,
256
- orig: #selector( URLSession . dataTask ( with: ) as ( URLSession ) -> ( URL ) -> URLSessionDataTask ) ,
257
- swizzled: #selector( URLSession . splunk_swizzled_dataTask ( with: ) as ( URLSession ) -> ( NSURL ) -> URLSessionDataTask ) )
258
-
259
- // @objc(overrrideName) requires a runtime lookup rather than a build-time lookup (seems like a bug in the compiler)
260
- swizzle ( clazz: urlsession,
261
- orig: #selector( URLSession . dataTask ( with: completionHandler: ) as ( URLSession ) -> ( URLRequest , @escaping ( Data ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDataTask ) ,
262
- swizzled: NSSelectorFromString ( " splunkSwizzledDataTaskWithRequest:completionHandler: " ) )
263
-
264
- swizzle ( clazz: urlsession,
265
- orig: #selector( URLSession . dataTask ( with: ) as ( URLSession ) -> ( URLRequest ) -> URLSessionDataTask ) ,
266
- swizzled: NSSelectorFromString ( " splunkSwizzledDataTaskWithRequest: " ) )
267
-
268
- // upload tasks
269
- swizzle ( clazz: urlsession,
270
- orig: #selector( URLSession . uploadTask ( with: from: ) ) ,
271
- swizzled: #selector( URLSession . splunk_swizzled_uploadTask ( with: from: ) ) )
272
- swizzle ( clazz: urlsession,
273
- orig: #selector( URLSession . uploadTask ( with: from: completionHandler: ) ) ,
274
- swizzled: #selector( URLSession . splunk_swizzled_uploadTask ( with: from: completionHandler: ) ) )
275
- swizzle ( clazz: urlsession,
276
- orig: #selector( URLSession . uploadTask ( with: fromFile: ) ) ,
277
- swizzled: #selector( URLSession . splunk_swizzled_uploadTask ( with: fromFile: ) ) )
278
- swizzle ( clazz: urlsession,
279
- orig: #selector( URLSession . uploadTask ( with: fromFile: completionHandler: ) ) ,
280
- swizzled: #selector( URLSession . splunk_swizzled_uploadTask ( with: fromFile: completionHandler: ) ) )
281
- swizzle ( clazz: urlsession,
282
- orig: #selector( URLSession . uploadTask ( withStreamedRequest: ) ) ,
283
- swizzled: #selector( URLSession . splunk_swizzled_uploadTask ( withStreamedRequest: ) ) )
284
-
285
- // download tasks
286
- swizzle ( clazz: urlsession,
287
- orig: #selector( URLSession . downloadTask ( with: ) as ( URLSession ) -> ( URL ) -> URLSessionDownloadTask ) ,
288
- swizzled: #selector( URLSession . splunk_swizzled_downloadTask ( with: ) as ( URLSession ) -> ( NSURL ) -> URLSessionDownloadTask ) )
289
- swizzle ( clazz: urlsession,
290
- orig: #selector( URLSession . downloadTask ( with: completionHandler: ) as ( URLSession ) -> ( URL , @escaping ( URL ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDownloadTask ) ,
291
- swizzled: #selector( URLSession . splunk_swizzled_downloadTask ( with: completionHandler: ) as ( URLSession ) -> ( NSURL , @escaping ( URL ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDownloadTask ) )
292
- swizzle ( clazz: urlsession,
293
- orig: #selector( URLSession . downloadTask ( with: completionHandler: ) as ( URLSession ) -> ( URLRequest , @escaping ( URL ? , URLResponse ? , Error ? ) -> Void ) -> URLSessionDownloadTask ) ,
294
- swizzled: NSSelectorFromString ( " splunkSwizzledDownloadTaskWithRequest:completionHandler: " ) )
295
- swizzle ( clazz: urlsession,
296
- orig: #selector( URLSession . downloadTask ( with: ) as ( URLSession ) -> ( URLRequest ) -> URLSessionDownloadTask ) ,
297
- swizzled: NSSelectorFromString ( " splunkSwizzledDownloadTaskWithRequest: " ) )
298
- // FIXME figure out how to support the two ResumeData variants - state transfer is weird
191
+ func swizzledUrlSessionClasses( ) -> [ AnyClass ] {
192
+ let conf = URLSessionConfiguration . ephemeral
193
+ let session = URLSession ( configuration: conf)
194
+ // The URL is just something parseable, since empty string can not be provided
195
+ let localDataTask = session. dataTask ( with: URL ( string: " https://splunkrum " ) !)
196
+
197
+ defer {
198
+ localDataTask. cancel ( )
199
+ session. finishTasksAndInvalidate ( )
200
+ }
201
+
202
+ let setStateSelector = NSSelectorFromString ( " setState: " )
203
+
204
+ var classes : [ AnyClass ] = [ ]
205
+ guard var currentClass: AnyClass = object_getClass ( localDataTask) else { return classes }
206
+ var method = class_getInstanceMethod ( currentClass, setStateSelector)
207
+
208
+ while method != nil {
209
+ let classResumeImp = method_getImplementation ( method!)
210
+
211
+ let superClass : AnyClass ? = currentClass. superclass ( )
212
+ let superClassMethod = class_getInstanceMethod ( superClass, setStateSelector)
213
+ let superClassResumeImp = superClassMethod. map { method_getImplementation ( $0) }
299
214
215
+ if classResumeImp != superClassResumeImp {
216
+ classes. append ( currentClass)
217
+ }
218
+
219
+ if superClass == nil {
220
+ return classes
221
+ }
222
+
223
+ currentClass = superClass!
224
+ method = superClassMethod
225
+ }
226
+
227
+ return classes
228
+ }
229
+
230
+ func swizzleUrlSession( ) {
231
+ let classes = swizzledUrlSessionClasses ( )
232
+
233
+ let setStateSelector = NSSelectorFromString ( " setState: " )
234
+ let resumeSelector = NSSelectorFromString ( " resume " )
235
+
236
+ for classToSwizzle in classes {
237
+ swizzle ( clazz: classToSwizzle, orig: setStateSelector, swizzled: #selector( URLSessionTask . splunk_swizzled_setState ( state: ) ) )
238
+ swizzle ( clazz: classToSwizzle, orig: resumeSelector, swizzled: #selector( URLSessionTask . splunk_swizzled_resume) )
239
+ }
240
+ }
241
+
242
+ func initalizeNetworkInstrumentation( ) {
243
+ swizzleUrlSession ( )
300
244
}
0 commit comments