Skip to content

Data Collection Custom Rules

View

You need to enable the configuration FTRUMConfig.enableTraceUserView = YES.

rumConfig.viewTrackingHandler = [CustomViewTracker new];

#import "FTDefaultUIKitViewTrackingHandler.h"

// Protocol implementation example
@interface CustomViewTracker : NSObject <FTUIKitViewTrackingHandler>
// Only add this when you need the SDK default view collection rules
@property (nonatomic, strong) FTDefaultUIKitViewTrackingHandler defaultHandler;
@end

@implementation CustomViewTracker

// Only add this when you need the SDK default view collection rules
-(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 the SDK 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 {

    // Only keep this property when you need the SDK default view collection rules
    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 the SDK 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 Auto Collection (Experimental)

SwiftUI View auto collection first extracts the page name via the SDK, then uses FTRumConfig.swiftUIViewTrackingHandler to decide whether to generate a RUM View. You can use the default handler to enable it directly, or implement FTSwiftUIViewTrackingHandler to filter the extracted SwiftUI View name and customize the reported View name and properties.

To explicitly control the page name and lifecycle in a specific SwiftUI View, see SwiftUI View Manual Collection.

Note: SwiftUI View auto collection is currently an experimental feature. The related APIs and collection behavior may change in future versions.

Before using, enable FTRumConfig.enableTraceUserView and set FTRumConfig.swiftUIViewTrackingHandler.

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

To customize the SwiftUI View collection rules, implement FTSwiftUIViewTrackingHandler. Return FTRUMView to collect the SwiftUI View, or return nil to skip 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

You need to enable the configuration FTRUMConfig.enableTraceUserAction = YES.

rumConfig.actionTrackingHandler = [CustomActionTracker new];

#import "FTDefaultActionTrackingHandler.h"

// Protocol implementation example
// On iOS, conform to the `FTUIPressRUMActionsHandler` protocol
// On tvOS, conform to the `FTUITouchRUMActionsHandler` protocol.
@interface CustomActionTracker : NSObject <FTUIPressRUMActionsHandler,FTUITouchRUMActionsHandler>
// Only add this when you need the SDK default Action collection rules
@property (nonatomic, strong) FTDefaultActionTrackingHandler defaultHandler;
@end
@implementation CustomActionTracker

// Only add this when you need the SDK default Action collection rules
-(FTDefaultActionTrackingHandler *)defaultHandler{
    if (!_defaultHandler) {
        _defaultHandler = [FTDefaultActionTrackingHandler new];
    }
    return _defaultHandler;
}

// Protocol method required by 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 on iOS
-(nullable FTRUMAction *)rumActionWithTargetView:(UIView *)targetView{
    if (view.accessibilityIdentifier){
       return [[FTRUMAction alloc] initWithActionName:view.accessibilityIdentifier];
    }

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

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

    // After customizing some Actions, use the SDK default collection rules for the remaining (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
// On iOS, conform to the `FTUIPressRUMActionsHandler` protocol
// On tvOS, conform to the `FTUITouchRUMActionsHandler` protocol.
class CustomActionTracker: NSObject, FTUIPressRUMActionsHandler, FTUITouchRUMActionsHandler {

    // Only add this when you need the SDK default Action collection rules
    lazy var defaultHandler: FTDefaultActionTrackingHandler = {
        FTDefaultActionTrackingHandler()
    }()

    // Protocol method required by 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 on iOS
    func rumAction(withTargetView targetView: UIView) -> FTRUMAction? {
        if let identifier = targetView.accessibilityIdentifier {
            return FTRUMAction(actionName: identifier)
        }

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

        // Return nil to skip tracking
        return nil 
    }

    // Protocol method required on tvOS
    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 the SDK default collection rules for the remaining (return the default handler's result)        
        return defaultHandler.rumAction(with: pressType, targetView: targetView)

        // Return nil to skip tracking
        return nil
    }
}

Resource

You need to enable the configuration FTRUMConfig.enableTraceUserResource = YES or customize collection via forwarding URLSession Delegate.

Filter Collection by 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 the property provider closure, you can return additional attributes to attach to the RUM Resource.

For example, you may 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 error occurs, RUM generates an Error data of type network_error. Some local URLSession errors, such as task.cancel, are normal logic in user programs and not erroneous data. In this case, you can use the sessionTaskErrorFilter callback to intercept and filter them out. Return YES to intercept (skip), NO to not intercept. After interception, the 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
}

Error

Through FTRumConfig.issueDataProvider, when the SDK automatically collects a Crash or ANR, you can add business fields to the corresponding RUM Error based on the current exception information, such as business scenario, feature module, or experiment group. This capability is supported in SDK 1.6.7 and above.

issueDataProvider only adds fields for automatically collected Crash and ANR; it does not enable Crash or ANR monitoring. Before using, enable enableTrackAppCrash or enableTrackAppANR depending on the collection target.

Configuration

Set issueDataProvider before starting RUM. Modifying the original FTRumConfig after RUM starts will not change the currently running Provider.

FTRumConfig *rumConfig = [[FTRumConfig alloc] initWithAppid:appid];
rumConfig.enableTrackAppCrash = YES;
rumConfig.enableTrackAppANR = YES;

// Prepare a read-only business snapshot before starting RUM to avoid performing time-consuming operations in the callback.
NSDictionary<NSString *, id> *businessContext = @{
    @"business_scene": @"checkout",
    @"release_channel": @"app_store"
};

rumConfig.issueDataProvider = ^NSDictionary<NSString *, id> * _Nullable(FTIssueInfo *issue) {
    NSMutableDictionary<NSString *, id> *fields = [businessContext mutableCopy];
    fields[@"issue_category"] =
        issue.category == FTIssueCategoryCrash ? @"crash" : @"anr";
    fields[@"historical_issue"] = @(issue.isHistorical);

    if (issue.threadName.length > 0) {
        fields[@"issue_thread_name"] = issue.threadName;
    }
    return fields;
};

[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];
let rumConfig = FTRumConfig(appid: appid)
rumConfig.enableTrackAppCrash = true
rumConfig.enableTrackAppANR = true

// Prepare a read-only business snapshot before starting RUM to avoid performing time-consuming operations in the callback.
let businessContext: [String: Any] = [
    "business_scene": "checkout",
    "release_channel": "app_store"
]

rumConfig.issueDataProvider = { issue in
    var fields = businessContext
    fields["issue_category"] =
        issue.category == .crash ? "crash" : "anr"
    fields["historical_issue"] = issue.isHistorical

    if let threadName = issue.threadName, !threadName.isEmpty {
        fields["issue_thread_name"] = threadName
    }
    return fields
}

FTMobileAgent.sharedInstance().startRum(withConfigOptions: rumConfig)

The Provider is called synchronously once for each automatically collected Error that meets the criteria. Returning nil or an empty dictionary means no custom fields are added for this error.

FTIssueInfo

The callback parameter FTIssueInfo is a read-only object that describes the Crash or ANR currently being processed.

Property Type Description
category FTIssueCategory Exception category: FTIssueCategoryCrash / Swift .crash for Crash, FTIssueCategoryANR / Swift .anr for ANR
errorType NSString Corresponding RUM Error type. Crash is ios_crash, ANR is anr_error
message NSString Error message, nil if unavailable
stack NSString Exception stack trace
occurredAtNanoseconds long long Time of exception occurrence, Unix timestamp in nanoseconds
appState NSString Application state used by the corresponding RUM Error
threadName NSString Exception thread name, nil if unavailable
historical BOOL Whether the exception was restored from persisted data; Objective-C getter is isHistorical, Swift uses isHistorical

The correspondence of category, errorType, and historical in auto-collection scenarios is as follows:

Scenario category errorType historical
Crash report restored on next app launch Crash ios_crash YES
ANR restored in the current process ANR anr_error NO
Watchdog ANR from previous process restored on next app launch ANR anr_error YES

When historical is YES, the exception time, stack, and app state in FTIssueInfo come from persisted exception data, but the Provider is executed when the app is restored and the report is processed. Therefore, the business state directly read by the Provider at runtime belongs to the current process, not necessarily the state at the time of the exception. To associate business information from the time of the exception, persist it in advance and load it as a thread-safe memory snapshot before starting RUM; do not perform disk reads inside the Provider.

Custom Field Rules

The fields returned by the Provider are written to the fields of the corresponding RUM Error, not to tags. Fields must meet the following rules:

  • Keys must be non-empty strings, UTF-8 encoded length must not exceed 100 bytes.
  • Values only support strings, booleans, integers, and finite floats; arrays, dictionaries, NSNull, or custom objects are not supported.
  • String values must have UTF-8 encoded length not exceeding 4096 bytes.
  • The SDK scans at most the first 50 items of the returned dictionary; invalid fields also count toward the scan count; dictionary traversal order is not fixed, so it is recommended to return no more than 50 items.
  • The estimated total size of all accepted fields must not exceed 25 KiB.
  • Keys starting with error. or error_ are ignored.
  • If a custom field has the same name as an existing SDK tag or field, the SDK field takes precedence.

Fields that do not meet the rules are ignored and do not prevent the original Crash or ANR Error from being collected and reported. Custom fields are uploaded with the RUM data; do not include sensitive information such as passwords or tokens.

Callback Execution Requirements

issueDataProvider may be called concurrently or reentrantly on a non-main thread. The callback must meet the following requirements:

  • Ensure thread safety, preferably by reading pre-prepared immutable data or memory snapshots.
  • It is recommended to return within 10 ms.
  • Do not manipulate the UI, perform network or disk I/O.
  • Do not perform thread switches, synchronous dispatches, or long waits for locks.

When execution time exceeds 50 ms, the SDK may output a slow callback debug log, but will still continue processing the original RUM Error.

Scope

issueDataProvider applies to automatically collected Crash, ANR restored in the current process, and Watchdog ANR from the previous process. The following data does not trigger this Provider:

  • Errors manually added via addError and similar interfaces
  • Resource / Network Errors
  • WebView Errors
  • Long Tasks
  • Data upload or retry processes

Custom Trace Header

You can set it globally via FTTraceConfig.traceInterceptor, or customize Trace at the URLSession level. The following uses W3C traceContext as an example.

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

    // 2. Get the SDK 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 tracing 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 custom traceId
        guard let replaceTrace = request.allHTTPHeaderFields?[CUSTOM_TRACE_HEADER] else {
            return nil
        }

        // 2. Get the SDK 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 tracing 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
    }

Feedback

Is this page helpful?