Skip to content

Data Collection Custom Rules

View

Requires enabling the configuration FTRUMConfig.enableTraceUserView = YES.

rumConfig.viewTrackingHandler = [CustomViewTracker new];

#import "FTDefaultUIKitViewTrackingHandler.h"

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

@implementation CustomViewTracker

// Add only when 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 some pages, use 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 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 some pages, use 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
    }
}

SwiftUI View Automatic Collection (Experimental)

SwiftUI View has two usage modes: Automatic Collection and Manual Collection.

  • Automatic Collection: Processes the SwiftUI View Name automatically extracted by the SDK via FTRumConfig.swiftUIViewTrackingHandler. Suitable for scenarios where you want to minimize business code changes, apply unified filtering, or naming.
  • Manual Collection: Explicitly marks a RUM View on a SwiftUI View using .ftTrackRUMView(name:property:). Suitable when page names need to be stable and controllable, when automatically extracted names don't meet expectations, or when you only want to collect specific SwiftUI pages. See SwiftUI View Manual Collection for usage.

Note: SwiftUI View automatic collection is currently an experimental feature. The API and collection behavior may change in future versions.

Requires enabling the configuration FTRUMConfig.enableTraceUserView = YES, and setting FTRumConfig.swiftUIViewTrackingHandler.

rumConfig.enableTraceUserView = YES;
rumConfig.swiftUIViewTrackingHandler = [FTDefaultSwiftUIViewTrackingHandler new];
rumConfig.enableTraceUserView = true
rumConfig.swiftUIViewTrackingHandler = FTDefaultSwiftUIViewTrackingHandler()

To customize SwiftUI View collection rules, implement FTSwiftUIViewTrackingHandler. Returning an FTRUMView indicates collecting this SwiftUI View, returning nil indicates skipping it.

rumConfig.swiftUIViewTrackingHandler = [CustomSwiftUIViewTracker new];

@interface CustomSwiftUIViewTracker : NSObject <FTSwiftUIViewTrackingHandler>
@end

@implementation CustomSwiftUIViewTracker
- (nullable FTRUMView *)rumViewForExtractedViewName:(NSString *)extractedViewName {
    if ([extractedViewName isEqualToString:@"HomeView"]) {
        return [[FTRUMView alloc] initWithViewName:@"main_home" property:@{@"page_type": @"home"}];
    }
    return nil;
}
@end
rumConfig.swiftUIViewTrackingHandler = CustomSwiftUIViewTracker()

class CustomSwiftUIViewTracker: NSObject, FTSwiftUIViewTrackingHandler {
    func rumView(forExtractedViewName extractedViewName: String) -> FTRUMView? {
        if extractedViewName == "HomeView" {
            return FTRUMView(
                viewName: "main_home",
                property: ["page_type": "home"]
            )
        }
        return nil
    }
}

Action

Requires enabling the configuration FTRUMConfig.enableTraceUserAction = YES.

rumConfig.actionTrackingHandler = [CustomActionTracker new];

#import "FTDefaultActionTrackingHandler.h"

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

// Add only when 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 iOS environment
-(nullable FTRUMAction *)rumActionWithTargetView:(UIView *)targetView{
    if (view.accessibilityIdentifier){
       return [[FTRUMAction alloc] initWithActionName:view.accessibilityIdentifier];
    }

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

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

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

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

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

    // Add only when 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 iOS environment
    func rumAction(withTargetView targetView: UIView) -> FTRUMAction? {
        if let identifier = targetView.accessibilityIdentifier {
            return FTRUMAction(actionName: identifier)
        }

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

        // Return nil to skip tracking
        return nil 
    }

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

        // After customizing some Actions, use SDK's default collection rules for the remaining ones (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 Network Collection via Forwarding URLSession Delegate

Filter Collection Based on URL

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

Add Custom Properties

By setting a property provider closure, you can return additional properties 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 of type network_error. Some URLSession local errors, like task.cancel, are normal program logic and not erroneous data. In such cases, you can intercept and filter them using the sessionTaskErrorFilter callback. Confirm interception by returning YES, do not intercept by returning NO. After interception, RUM-Error will not collect that 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;
    // Keep 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
        // Keep the SDK-generated spanId (fixed position at index 2)
        context.spanId = newComponents[2]
        return context
    }