Skip to content

URLSession Custom Network Collection

Customizing Network Collection via URLSession Delegate Forwarding

The SDK provides a class FTURLSessionDelegate, which can be used to customize RUM Resource collection and trace linking for network requests initiated by a specific URLSession.

  • FTURLSessionDelegate supports intercepting URLRequest through the traceInterceptor block for custom trace linking (supported in SDK version 1.5.9 and above). Priority: > FTTraceConfig.traceInterceptor
  • FTURLSessionDelegate supports customizing additional attributes to be collected for RUM Resources through the provider block. Priority: > FTRumConfig.resourcePropertyProvider
  • FTURLSessionDelegate supports customizing whether to intercept SessionTask Errors through the errorFilter block (supported in SDK version 1.5.17 and above)
    • return YES: Intercept, this network_error will not be added to RUM-Error
    • return NO: Do not intercept, this network_error will be added to RUM-Error
  • When used together with FTRumConfig.enableTraceUserResource and FTTraceConfig.enableAutoTrace, priority: Custom > Automatic Collection

Three methods are provided below to meet different scenarios.

Method 1

Directly set the delegate object of the URLSession to an instance of FTURLSessionDelegate.

id<NSURLSessionDelegate> delegate = [[FTURLSessionDelegate alloc]init];
// Add custom RUM resource attributes. It is recommended to prefix tag names with the project abbreviation, e.g., `df_tag_name`.
delegate.provider = ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
                NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
                return @{@"df_requestbody":body};
            };
// Supports custom trace. If interception is confirmed, return TraceContext; if not, return nil
delegate.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
        FTTraceContext *context = [FTTraceContext new];
        context.traceHeader = @{@"trace_key":@"trace_value"};
        context.traceId = @"trace_id";
        context.spanId = @"span_id";
        return context;
    };
// Customize whether to intercept SessionTask Error. return YES: intercept, this `network_error` will not be added to RUM-Error
delegate.errorFilter = ^BOOL(NSError * _Nonnull error) {
        return error.code == NSURLErrorCancelled;
    };
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:delegate delegateQueue:nil];
let delegate = FTURLSessionDelegate.init()
// Add custom RUM resource attributes. It is recommended to prefix tag names with the project abbreviation, e.g., `df_tag_name`.
delegate.provider = { request,response,data,error in
            var extraData:Dictionary<String, Any> = Dictionary()
            if let data = data,let requestBody = String(data: data, encoding: .utf8) {
                extraData["df_requestBody"] = requestBody
            }
            if let error = error {
                extraData["df_error"] = error.localizedDescription
            }
            return extraData
        }
// Supports custom trace. If interception is confirmed, return TraceContext; if not, return nil
delegate.traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }
delegate.errorFilter = { error in
    return (error as? URLError)?.code == .cancelled
}
let session =  URLSession.init(configuration: URLSessionConfiguration.default, delegate:delegate
, delegateQueue: nil)

Method 2

Make the delegate object of the URLSession inherit from the FTURLSessionDelegate class.

If the delegate object implements the following methods, ensure to call the corresponding parent class methods within them.

  • -URLSession:dataTask:didReceiveData:
  • -URLSession:task:didCompleteWithError:
  • -URLSession:task:didFinishCollectingMetrics:
@interface InstrumentationInheritClass:FTURLSessionDelegate
@property (nonatomic, strong) NSURLSession *session;
@end
@implementation InstrumentationInheritClass
-(instancetype)init{
    self = [super init];
    if(self){
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
        // Add custom RUM resource attributes. It is recommended to prefix tag names with the project abbreviation, e.g., `df_tag_name`.
        self.provider = ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
        NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
        return @{@"df_requestbody":body};
    };
        // Supports custom trace. If interception is confirmed, return TraceContext; if not, return nil
       self.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
        FTTraceContext *context = [FTTraceContext new];
        context.traceHeader = @{@"trace_key":@"trace_value"};
        context.traceId = @"trace_id";
        context.spanId = @"span_id";
        return context;
       };
       // Customize whether to intercept SessionTask Error. return YES: intercept, this `network_error` will not be added to RUM-Error
       self.errorFilter = ^BOOL(NSError * _Nonnull error) {
          return error.code == NSURLErrorCancelled;
       };
    }
    return self;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
    // Must call the parent class method
    [super URLSession:session task:task didFinishCollectingMetrics:metrics];
    // Your own logic
    // ......
}
@end
class InheritHttpEngine:FTURLSessionDelegate {
    var session:URLSession?
    override init(){
        session = nil
        super.init()
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        session = URLSession.init(configuration: configuration, delegate:self, delegateQueue: nil)
        override init() {
        super.init()
        // Add custom RUM resource attributes. It is recommended to prefix tag names with the project abbreviation, e.g., `df_tag_name`.
        provider = { request,response,data,error in
            var extraData:Dictionary<String, Any> = Dictionary()
            if let data = data,let requestBody = String(data: data, encoding: .utf8) {
                extraData["df_requestBody"] = requestBody
            }
            if let error = error {
                extraData["df_error"] = error.localizedDescription
            }
            return extraData
        }
        // Supports custom trace. If interception is confirmed, return TraceContext; if not, return nil
        traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }
        errorFilter = { error in
            return (error as? URLError)?.code == .cancelled
        }
    }
    }

    override func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
        // Must call the parent class method
        super.urlSession(session, task: task, didFinishCollecting: metrics)
        // User's own logic
        // ......
    }
}

Method 3

Make the delegate object of the URLSession conform to the FTURLSessionDelegateProviding protocol.

  • Implement the getter for the ftURLSessionDelegate property in the protocol
  • Forward the following URLSession delegate methods to ftURLSessionDelegate to enable SDK data collection
    • -URLSession:dataTask:didReceiveData:
    • -URLSession:task:didCompleteWithError:
    • -URLSession:task:didFinishCollectingMetrics:
@interface UserURLSessionDelegateClass:NSObject<NSURLSessionDataDelegate,FTURLSessionDelegateProviding>
@end
@implementation UserURLSessionDelegateClass
@synthesize ftURLSessionDelegate = _ftURLSessionDelegate;

- (nonnull FTURLSessionDelegate *)ftURLSessionDelegate {
    if(!_ftURLSessionDelegate){
        _ftURLSessionDelegate = [[FTURLSessionDelegate alloc]init];
         // Add custom RUM resource attributes. It is recommended to prefix tag names with the project abbreviation, e.g., `df_tag_name`.
        _ftURLSessionDelegate.provider =  ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
                NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
                return @{@"df_requestbody":body};
            };
          // Supports custom trace. If interception is confirmed, return TraceContext; if not, return nil
        _ftURLSessionDelegate.requestInterceptor = ^NSURLRequest * _Nonnull(NSURLRequest * _Nonnull request) {
            NSDictionary *traceHeader = [[FTExternalDataManager sharedManager] getTraceHeaderWithUrl:request.URL];
            NSMutableURLRequest *newRequest = [request mutableCopy];
            if(traceHeader){
                for (NSString *key in traceHeader.allKeys) {
                    [newRequest setValue:traceHeader[key] forHTTPHeaderField:key];
                }
            }
            return newRequest;
        };
        // Customize whether to intercept SessionTask Error. return YES: intercept, this `network_error` will not be added to RUM-Error
       _ftURLSessionDelegate.errorFilter = ^BOOL(NSError * _Nonnull error) {
           return error.code == NSURLErrorCancelled;
       };
    }
    return _ftURLSessionDelegate;
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    [self.ftURLSessionDelegate URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    [self.ftURLSessionDelegate URLSession:session task:task didCompleteWithError:error];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
    [self.ftURLSessionDelegate URLSession:session task:task didFinishCollectingMetrics:metrics];
}
@end
class HttpEngine:NSObject,URLSessionDataDelegate,FTURLSessionDelegateProviding {
    var ftURLSessionDelegate: FTURLSessionDelegate = FTURLSessionDelegate()
    var session:URLSession?

    override init(){
        session = nil
        super.init()
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        session = URLSession.init(configuration: configuration, delegate:self, delegateQueue: nil)
        // Add custom RUM resource attributes. It is recommended to prefix tag names with the project abbreviation, e.g., `df_tag_name`.
        ftURLSessionDelegate.provider = { request,response,data,error in
            var extraData:Dictionary<String, Any> = Dictionary()
            if let data = data,let requestBody = String(data: data, encoding: .utf8) {
                extraData["df_requestBody"] = requestBody
            }
            if let error = error {
                extraData["df_error"] = error.localizedDescription
            }
            return extraData
        }
        // Supports custom trace. If interception is confirmed, return TraceContext; if not, return nil
        ftURLSessionDelegate.traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }
        ftURLSessionDelegate.errorFilter = { error in
            return (error as? URLError)?.code == .cancelled
        }
    }
    // The following methods must be implemented
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        ftURLSessionDelegate.urlSession(session, dataTask: dataTask, didReceive: data)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
        ftURLSessionDelegate.urlSession(session, task: task, didFinishCollecting: metrics)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        ftURLSessionDelegate.urlSession(session, task: task, didCompleteWithError: error)
    }
}