Skip to content

Data Collection Custom Rules

View

Requires enabling the configuration FTRUMConfig.enableTraceUserView = YES.

rumConfig.viewTrackingHandler = [CustomViewTracker new];

#import "FTDefaultUIKitViewTrackingHandler.h"

// Example of protocol implementation
@interface CustomViewTracker : NSObject <FTUIKitViewTrackingHandler>
// Add only when the SDK's default view collection rules are needed
@property (nonatomic, strong) FTDefaultUIKitViewTrackingHandler defaultHandler;
@end

@implementation CustomViewTracker

// Add only when the SDK's default view collection rules are needed
-(FTDefaultUIKitViewTrackingHandler *)defaultHandler{
    if (!_defaultHandler) {
        _defaultHandler = [FTDefaultUIKitViewTrackingHandler new];
    }
    return _defaultHandler;
}

- (FTRUMView *)rumViewForViewController:(UIViewController *)viewController {
    // Exact match by class name
    if ([viewController isKindOfClass:[HomeViewController class]]) {
        return [[FTRUMView alloc] initWithViewName:@"main_home" property:@{@"page_type": @"home"}];
    }
    // Filter by prefix
    else if ([NSStringFromClass([viewController class]) hasPrefix:@"FT"]) {
        return [[FTRUMView alloc] initWithViewName:[NSString stringWithFormat:@"ft_%@", NSStringFromClass([viewController class])] property:nil];
    }
    // Set via accessibilityLabel
    else if (viewController.view.accessibilityLabel) {
        return [[FTRUMView alloc] initWithViewName:viewController.view.accessibilityLabel property:nil];
    }
    // After customizing specific pages, use the SDK's default collection rules for the remaining pages (return the default handler's result)
    return [self.defaultHandler rumViewForViewController:viewController];

    // To skip tracking, simply return nil
    return nil;
}
@end
rumConfig.viewTrackingHandler = CustomViewTracker()

class CustomViewTracker: NSObject, FTUIKitViewTrackingHandler {

    // Keep this property only when the SDK's default view collection rules are needed
    lazy var defaultHandler: FTDefaultUIKitViewTrackingHandler = {
        FTDefaultUIKitViewTrackingHandler() 
    }()

    func rumView(for viewController: UIViewController) -> FTRUMView? {
        // Exact match by class name
        if viewController is HomeViewController {
            let properties: [String: Any] = ["page_type": "home"]
            return FTRUMView(viewName: "main_home", property: properties)
        }

        // Filter by class name prefix
        let vcClassName = String(describing: type(of: viewController))
        if vcClassName.hasPrefix("FT") {
            let viewName = "ft_\(vcClassName)"
            return FTRUMView(viewName: viewName, property: nil)
        }

        // Set via accessibilityLabel
        if let accessibilityLabel = viewController.view.accessibilityLabel, !accessibilityLabel.isEmpty {
            return FTRUMView(viewName: accessibilityLabel, property: nil)
        }

        // After customizing specific pages, use the SDK's default collection rules for the remaining pages (return the default handler's result)
        return defaultHandler.rumView(for: viewController)

        // To skip tracking, simply return nil
        return nil
    }
}

Action

Requires enabling the configuration FTRUMConfig.enableTraceUserAction = YES.

rumConfig.actionTrackingHandler = [CustomActionTracker new];

#import "FTDefaultActionTrackingHandler.h"

// Example of protocol implementation
// In the iOS environment, conform to the `FTUIPressRUMActionsHandler` protocol.
// In the tvOS environment, conform to the `FTUITouchRUMActionsHandler` protocol.
@interface CustomActionTracker : NSObject <FTUIPressRUMActionsHandler,FTUITouchRUMActionsHandler>
// Add only when the SDK's default Action collection rules are needed
@property (nonatomic, strong) FTDefaultActionTrackingHandler defaultHandler;
@end
@implementation CustomActionTracker

// Add only when the SDK's default Action collection rules are needed
-(FTDefaultActionTrackingHandler *)defaultHandler{
    if (!_defaultHandler) {
        _defaultHandler = [FTDefaultActionTrackingHandler new];
    }
    return _defaultHandler;
}

// Protocol method required for both iOS and tvOS
- (nullable FTRUMAction *)rumLaunchActionWithLaunchType:(FTLaunchType)type {
    if(type == FTLaunchCold){
      return [[FTRUMAction alloc]initWithActionName:@"cold"];
    }
    // Return nil to skip tracking
    return nil;
}

// Protocol method required for the iOS environment
-(nullable FTRUMAction *)rumActionWithTargetView:(UIView *)targetView{
    if (view.accessibilityIdentifier){
       return [[FTRUMAction alloc] initWithActionName:view.accessibilityIdentifier];
    }

     // After customizing specific Actions, use the SDK's default collection rules for the rest (return the default handler's result)
    return [self.defaultHandler rumActionWithTargetView:targetView];

    // Return nil to skip tracking
    return nil;
}
// Protocol method required for the tvOS environment
- (nullable FTRUMAction *)rumActionWithPressType:(UIPressType)type targetView:(UIView *)targetView{
    if (type == UIPressTypeSelect && view.accessibilityIdentifier){
       return [[FTRUMAction alloc] initWithActionName:view.accessibilityIdentifier];
    }

    // After customizing specific Actions, use the SDK's default collection rules for the rest (return the default handler's result)
    return [self.defaultHandler rumActionWithPressType:type targetView:targetView];

        // Return nil to skip tracking
    return nil;
}
@end
rumConfig.actionTrackingHandler = CustomActionTracker()

// Example of protocol implementation
// In the iOS environment, conform to the `FTUIPressRUMActionsHandler` protocol.
// In the tvOS environment, conform to the `FTUITouchRUMActionsHandler` protocol.
class CustomActionTracker: NSObject, FTUIPressRUMActionsHandler, FTUITouchRUMActionsHandler {

    // Add only when the SDK's default Action collection rules are needed
    lazy var defaultHandler: FTDefaultActionTrackingHandler = {
        FTDefaultActionTrackingHandler()
    }()

    // Protocol method required for both iOS and tvOS
    func rumLaunchAction(with type: FTLaunchType) -> FTRUMAction? {
        if type == .cold {
            return FTRUMAction(actionName: "cold")
        }
        // Return nil to skip tracking
        return nil
    }

    // Protocol method required for the iOS environment
    func rumAction(withTargetView targetView: UIView) -> FTRUMAction? {
        if let identifier = targetView.accessibilityIdentifier {
            return FTRUMAction(actionName: identifier)
        }

        // After customizing specific Actions, use the SDK's default collection rules for the rest (return the default handler's result)
        return defaultHandler.rumAction(withTargetView: targetView)

        // Return nil to skip tracking
        return nil 
    }

    // Protocol method required for the tvOS environment
    func rumAction(with pressType: UIPress.PressType, targetView: UIView) -> FTRUMAction? {
        if pressType == .select, let identifier = targetView.accessibilityIdentifier {
            return FTRUMAction(actionName: identifier)
        }

        // After customizing specific Actions, use the SDK's default collection rules for the rest (return the default handler's result)        
        return defaultHandler.rumAction(with: pressType, targetView: targetView)

        // Return nil to skip tracking
        return nil
    }
}

Resource

Requires enabling the configuration FTRUMConfig.enableTraceUserResource = YES or custom collection via forwarding URLSession Delegate.

Filter Collection Based on URL

rumConfig.resourceUrlHandler = ^(NSURL *url){
        // Return YES to skip collection; Return NO to collect.
        if ([url.host isEqualToString:@"example.com"]) {
            return YES;
        }
        return NO;
};
rumConfig.resourceUrlHandler = { url in 
     // Return true to skip collection; Return false to collect.
     return url.host == "example.com"
}

Add Custom Attributes

By setting an attribute provider closure, you can return additional attributes to be attached to the RUM Resource.

For example, you might want to add the HTTP request body to the RUM Resource:

rumConfig.resourcePropertyProvider = ^NSDictionary *_Nullable(NSURLRequest *request, NSURLResponse *response,NSData *data, NSError *error) {
     NSString *body = @"";
     if (request.HTTPBody) {
        body = [[NSString alloc] initWithData:httpBody encoding:NSUTF8StringEncoding] ?: @"";
     }
     return @{@"request_body": body};
 }
rumConfig.resourcePropertyProvider = { request, response, data, error in
   let body = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? ""
   return ["request_body": body]
  }

Filter Network Errors

When a network request encounters an error, RUM generates an Error data entry of type network_error. Some URLSession local errors, such as task.cancel, are part of the normal program logic and are not erroneous data. In such cases, you can intercept and filter them using the sessionTaskErrorFilter callback. Return YES to confirm interception, NO to not intercept. After interception, RUM-Error will not collect this error.

rumConfig.sessionTaskErrorFilter = ^BOOL(NSError * _Nonnull error){
    return error.code == NSURLErrorCancelled;
}; 
rumConfig.sessionTaskErrorFilter = { error in
   return (error as? URLError)?.code == .cancelled
}

Custom TraceHeader

Can be set globally via FTTraceConfig.traceInterceptor or URLSession-level custom Trace. The following example uses w3c-traceContext.

FTTraceConfig *traceConfig = [[FTTraceConfig alloc]init];
   traceConfig.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
    // 1. Get the business-customized traceId
    NSString *replaceTrace = [request.allHTTPHeaderFields valueForKey:CUSTOM_TRACE_HEADER];

    // 2. Get the SDK's standard W3C traceparent request header
    NSDictionary *traceHeaders = [[FTExternalDataManager sharedManager] getTraceHeaderWithUrl:request.URL];
    NSString *traceParentStr = traceHeaders[FT_NETWORK_TRACEPARENT_KEY];

    // 3. Parse the W3C traceparent format and replace the traceId at index 1
    NSArray *traceComponents = [traceParentStr componentsSeparatedByString:@"-"];
    if (traceComponents.count != 4) {
        return nil;
    }
    NSMutableArray *newComponents = [traceComponents mutableCopy];
    newComponents[1] = replaceTrace;
    NSString *newTraceParent = [newComponents componentsJoinedByString:@"-"];

    // 4. Assemble and return the custom trace context
    FTTraceContext *context = [FTTraceContext new];
    context.traceHeader = @{FT_NETWORK_TRACEPARENT_KEY:newTraceParent};
    context.traceId = replaceTrace;
    // Preserve the SDK-generated spanId (fixed position at index 2)
    context.spanId = newComponents[2];
    return context;
};
let traceConfig = FTTraceConfig()
traceConfig.traceInterceptor = { (request: URLRequest) -> FTTraceContext? in
        // 1. Get the business-customized traceId
        guard let replaceTrace = request.allHTTPHeaderFields?[CUSTOM_TRACE_HEADER] else {
            return nil
        }

        // 2. Get the SDK's standard W3C traceparent request header
        guard let traceHeaders = FTExternalDataManager.shared().getTraceHeader(with: request.url!), let traceParentStr = traceHeaders[FT_NETWORK_TRACEPARENT_KEY] as? String else {
            return nil
        }

        // 3. Parse the W3C traceparent format and replace the traceId at index 1
        let traceComponents = traceParentStr.components(separatedBy: "-")
        guard traceComponents.count == 4 else {
            return nil
        }
        var newComponents = traceComponents
        newComponents[1] = replaceTrace
        let newTraceParent = newComponents.joined(separator: "-")

        // 4. Assemble and return the custom trace context
        let context = FTTraceContext()
        context.traceHeader = [FT_NETWORK_TRACEPARENT_KEY: newTraceParent]
        context.traceId = replaceTrace
        // Preserve the SDK-generated spanId (fixed position at index 2)
        context.spanId = newComponents[2]
        return context
    }