跳转至

数据采集自定义规则

View

需要开启配置 FTRUMConfig.enableTraceUserView = YES

rumConfig.viewTrackingHandler = [CustomViewTracker new];

#import "FTDefaultUIKitViewTrackingHandler.h"

// 协议实现方式示例
@interface CustomViewTracker : NSObject <FTUIKitViewTrackingHandler>
// 仅在需要 SDK 默认的 view 采集规则时添加
@property (nonatomic, strong) FTDefaultUIKitViewTrackingHandler defaultHandler;
@end

@implementation CustomViewTracker

// 仅在需要 SDK 默认的 view 采集规则时添加
-(FTDefaultUIKitViewTrackingHandler *)defaultHandler{
    if (!_defaultHandler) {
        _defaultHandler = [FTDefaultUIKitViewTrackingHandler new];
    }
    return _defaultHandler;
}

- (FTRUMView *)rumViewForViewController:(UIViewController *)viewController {
    // 通过类名精确匹配
    if ([viewController isKindOfClass:[HomeViewController class]]) {
        return [[FTRUMView alloc] initWithViewName:@"main_home" property:@{@"page_type": @"home"}];
    }
    // 通过前缀过滤
    else if ([NSStringFromClass([viewController class]) hasPrefix:@"FT"]) {
        return [[FTRUMView alloc] initWithViewName:[NSString stringWithFormat:@"ft_%@", NSStringFromClass([viewController class])] property:nil];
    }
    // 通过 accessibilityLabel 设置
    else if (viewController.view.accessibilityLabel) {
        return [[FTRUMView alloc] initWithViewName:viewController.view.accessibilityLabel property:nil];
    }
    // 自定义部分页面后,剩余页面使用 SDK 默认采集规则(返回默认处理器的处理结果)
    return [self.defaultHandler rumViewForViewController:viewController];

    // 若需要跳过追踪,直接返回 nil 即可
    return nil;
}
@end
rumConfig.viewTrackingHandler = CustomViewTracker()

class CustomViewTracker: NSObject, FTUIKitViewTrackingHandler {

    // 仅在需要 SDK 默认 view 采集规则时保留该属性
    lazy var defaultHandler: FTDefaultUIKitViewTrackingHandler = {
        FTDefaultUIKitViewTrackingHandler() 
    }()

    func rumView(for viewController: UIViewController) -> FTRUMView? {
        // 通过类名精确匹配
        if viewController is HomeViewController {
            let properties: [String: Any] = ["page_type": "home"]
            return FTRUMView(viewName: "main_home", property: properties)
        }

        // 通过类名前缀过滤
        let vcClassName = String(describing: type(of: viewController))
        if vcClassName.hasPrefix("FT") {
            let viewName = "ft_\(vcClassName)"
            return FTRUMView(viewName: viewName, property: nil)
        }

        // 通过 accessibilityLabel 设置
        if let accessibilityLabel = viewController.view.accessibilityLabel, !accessibilityLabel.isEmpty {
            return FTRUMView(viewName: accessibilityLabel, property: nil)
        }

        // 自定义部分页面后,剩余页面使用 SDK 默认采集规则(返回默认处理器的处理结果)
        return defaultHandler.rumView(for: viewController)

        // 若需要跳过追踪,直接返回 nil 即可
        return nil
    }
}

SwiftUI View 自动采集(实验性)

SwiftUI View 自动采集会先由 SDK 提取页面名称,再通过 FTRumConfig.swiftUIViewTrackingHandler 决定是否生成 RUM View。可以使用默认处理器直接启用,也可以实现 FTSwiftUIViewTrackingHandler,对提取到的 SwiftUI View Name 进行筛选,并自定义上报的 View Name 和属性。

如需在具体 SwiftUI View 中显式控制页面名称和生命周期,请参见 SwiftUI View 手动采集

注意:SwiftUI View 自动采集目前为实验性功能,相关 API 和采集行为可能在后续版本中调整。

使用前需要开启 FTRumConfig.enableTraceUserView,并设置 FTRumConfig.swiftUIViewTrackingHandler

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

如需自定义 SwiftUI View 采集规则,可实现 FTSwiftUIViewTrackingHandler。返回 FTRUMView 表示采集该 SwiftUI View,返回 nil 表示跳过。

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

需要开启配置 FTRUMConfig.enableTraceUserAction = YES

rumConfig.actionTrackingHandler = [CustomActionTracker new];

#import "FTDefaultActionTrackingHandler.h"

// 协议实现方式示例
// 在 iOS 环境需遵循 `FTUIPressRUMActionsHandler` 协议
// 在 tvOS 环境 ,需遵循 `FTUITouchRUMActionsHandler` 协议。
@interface CustomActionTracker : NSObject <FTUIPressRUMActionsHandler,FTUITouchRUMActionsHandler>
// 仅在需要 SDK 默认的 Action 采集规则时添加
@property (nonatomic, strong) FTDefaultActionTrackingHandler defaultHandler;
@end
@implementation CustomActionTracker

// 仅在需要 SDK 默认的 Action 采集规则时添加
-(FTDefaultActionTrackingHandler *)defaultHandler{
    if (!_defaultHandler) {
        _defaultHandler = [FTDefaultActionTrackingHandler new];
    }
    return _defaultHandler;
}

// iOS 与 tvOS 都需要实现的协议方法
- (nullable FTRUMAction *)rumLaunchActionWithLaunchType:(FTLaunchType)type {
    if(type == FTLaunchCold){
      return [[FTRUMAction alloc]initWithActionName:@"cold"];
    }
    // 返回 nil 跳过跟踪
    return nil;
}

// iOS 环境需实现的协议方法
-(nullable FTRUMAction *)rumActionWithTargetView:(UIView *)targetView{
    if (view.accessibilityIdentifier){
       return [[FTRUMAction alloc] initWithActionName:view.accessibilityIdentifier];
    }

     // 自定义部分 Action 后,剩余使用 SDK 默认采集规则(返回默认处理器的处理结果)
    return [self.defaultHandler rumActionWithTargetView:targetView];

    // 返回 nil 跳过跟踪
    return nil;
}
// tvOS 环境需实现的协议方法
- (nullable FTRUMAction *)rumActionWithPressType:(UIPressType)type targetView:(UIView *)targetView{
    if (type == UIPressTypeSelect && view.accessibilityIdentifier){
       return [[FTRUMAction alloc] initWithActionName:view.accessibilityIdentifier];
    }

    // 自定义部分 Action 后,剩余使用 SDK 默认采集规则(返回默认处理器的处理结果)
    return [self.defaultHandler rumActionWithPressType:type targetView:targetView];

        // 返回 nil 跳过跟踪
    return nil;
}
@end
rumConfig.actionTrackingHandler = CustomActionTracker()

// 协议实现方式示例
// 在 iOS 环境需遵循 `FTUIPressRUMActionsHandler` 协议
// 在 tvOS 环境 ,需遵循 `FTUITouchRUMActionsHandler` 协议。
class CustomActionTracker: NSObject, FTUIPressRUMActionsHandler, FTUITouchRUMActionsHandler {

    // 仅在需要 SDK 默认的 Action 采集规则时添加
    lazy var defaultHandler: FTDefaultActionTrackingHandler = {
        FTDefaultActionTrackingHandler()
    }()

    // iOS 与 tvOS 都需要实现的协议方法
    func rumLaunchAction(with type: FTLaunchType) -> FTRUMAction? {
        if type == .cold {
            return FTRUMAction(actionName: "cold")
        }
        // 返回 nil 跳过跟踪
        return nil
    }

    // iOS 环境需实现的协议方法
    func rumAction(withTargetView targetView: UIView) -> FTRUMAction? {
        if let identifier = targetView.accessibilityIdentifier {
            return FTRUMAction(actionName: identifier)
        }

        // 自定义部分 Action 后,剩余使用 SDK 默认采集规则(返回默认处理器的处理结果)
        return defaultHandler.rumAction(withTargetView: targetView)

        // 返回 nil 跳过跟踪
        return nil 
    }

    // tvOS 环境需实现的协议方法
    func rumAction(with pressType: UIPress.PressType, targetView: UIView) -> FTRUMAction? {
        if pressType == .select, let identifier = targetView.accessibilityIdentifier {
            return FTRUMAction(actionName: identifier)
        }

        // 自定义部分 Action 后,剩余使用 SDK 默认采集规则(返回默认处理器的处理结果)        
        return defaultHandler.rumAction(with: pressType, targetView: targetView)

        // 返回 nil 跳过跟踪
        return nil
    }
}

Resource

需要开启配置 FTRUMConfig.enableTraceUserResource = YES通过转发 URLSession Delegate 自定义采集 Network

根据 Url 采集过滤

rumConfig.resourceUrlHandler = ^(NSURL *url){
        // 返回 YES 表示不采集;返回 NO 表示采集
        if ([url.host isEqualToString:@"example.com"]) {
            return YES;
        }
        return NO;
};
rumConfig.resourceUrlHandler = { url in 
     // 返回 true 表示不采集;返回 false 表示采集
     return url.host == "example.com"
}

添加自定义属性

通过设置属性提供程序闭包,您可以返回要附加到 RUM Resoucre 的其他属性。

例如,您可能希望向 RUM Resource 添加 HTTP 请求 body:

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]
  }

过滤网络错误

网络请求发生错误时,RUM 中会生成一条类型为 network_error 的 Error 数据,一些 URLSeesion 的本地错误比如 task.cancel 是用户程序中的正常逻辑,并非是错误数据,此时可以通过 sessionTaskErrorFilter 回调进行拦截过滤。确认拦截返回 YES,不拦截返回 NO,拦截后 RUM-Error 不采集该条错误。

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

Error

通过 FTRumConfig.issueDataProvider,可以在 SDK 自动采集 Crash 或 ANR 时,根据本次异常的信息为对应的 RUM Error 添加业务字段,例如业务场景、功能模块或实验分组。该能力在 SDK 1.6.7 及以上版本支持。

issueDataProvider 只为自动采集的 Crash 和 ANR 添加字段,不会开启 Crash 或 ANR 监控。使用前需要根据采集目标开启 enableTrackAppCrashenableTrackAppANR

配置

请在启动 RUM 前设置 issueDataProvider。RUM 启动后再修改原 FTRumConfig,不会改变当前运行中的 Provider。

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

// 在启动 RUM 前准备只读的业务快照,避免在回调中执行耗时操作。
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

// 在启动 RUM 前准备只读的业务快照,避免在回调中执行耗时操作。
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)

Provider 会为每个符合条件的自动采集 Error 同步调用一次。返回 nil 或空字典表示本次不添加自定义字段。

FTIssueInfo

回调参数 FTIssueInfo 是只读对象,描述当前正在处理的 Crash 或 ANR。

属性 类型 说明
category FTIssueCategory 异常类别:FTIssueCategoryCrash / Swift .crash 表示 Crash,FTIssueCategoryANR / Swift .anr 表示 ANR
errorType NSString 对应 RUM Error 的类型。Crash 为 ios_crash,ANR 为 anr_error
message NSString Error 消息,无法获取时为 nil
stack NSString 异常堆栈
occurredAtNanoseconds long long 异常发生时间,Unix 时间戳,单位为纳秒
appState NSString 对应 RUM Error 使用的应用状态
threadName NSString 异常线程名称,无法获取时为 nil
historical BOOL 是否根据持久化数据还原异常;Objective-C getter 为 isHistorical,Swift 使用 isHistorical

自动采集场景中的 categoryerrorTypehistorical 对应关系如下:

场景 category errorType historical
Crash 报告在应用下次启动时恢复 Crash ios_crash YES
当前进程恢复后的 ANR ANR anr_error NO
上一个进程的 Watchdog ANR 在应用下次启动时恢复 ANR anr_error YES

historicalYES 时,FTIssueInfo 中的异常时间、堆栈和应用状态来自已持久化的异常数据,但 Provider 在应用恢复并处理报告时才执行。因此,Provider 运行时直接读取到的业务状态属于当前进程,不一定是异常发生时的状态。如需关联异常发生时的业务信息,请提前持久化,并在启动 RUM 前加载为线程安全的内存快照;不要在 Provider 内进行磁盘读取。

自定义字段规则

Provider 返回的字段会写入对应 RUM Error 的 fields,不会写入 tags。字段需要满足以下规则:

  • Key 必须是非空字符串,UTF-8 编码长度不能超过 100 字节。
  • Value 仅支持字符串、布尔值、整数和有限浮点数;不支持数组、字典、NSNull 或自定义对象。
  • 字符串 Value 的 UTF-8 编码长度不能超过 4096 字节。
  • SDK 最多扫描返回字典中的前 50 项,无效字段也会占用扫描数量;字典遍历顺序不固定,建议返回不超过 50 项。
  • 所有被接受字段的估算总大小不能超过 25 KiB。
  • error.error_ 开头的 Key 会被忽略。
  • 自定义字段与 SDK 已有 tag 或 field 重名时,以 SDK 字段为准。

不符合规则的字段会被忽略,不会阻止原 Crash 或 ANR Error 的采集和上报。自定义字段会随 RUM 数据上传,请勿加入密码、Token 等敏感信息。

回调执行要求

issueDataProvider 可能在非主线程上并发或重入执行。回调应满足以下要求:

  • 保证实现线程安全,优先读取已准备好的不可变数据或内存快照。
  • 建议在 10 ms 内返回。
  • 不要操作 UI,不要执行网络或磁盘 I/O。
  • 不要进行线程切换、同步派发、长时间等待锁等阻塞操作。

当执行时间超过 50 ms 时,SDK 可能输出慢回调调试日志,但仍会继续处理原 RUM Error。

适用范围

issueDataProvider 适用于 SDK 自动采集的 Crash、当前进程恢复后的 ANR,以及上一个进程的 Watchdog ANR。以下数据不会触发该 Provider:

  • 通过 addError 等接口手动添加的 Error
  • Resource / Network Error
  • WebView Error
  • Long Task
  • 数据上传或重试过程

自定义 TraceHeader

可以通过 FTTraceConfig.traceInterceptor 进行全局设置,或 URLSession 级自定义 Trace,以下为 w3c-traceContext 为例

FTTraceConfig *traceConfig = [[FTTraceConfig alloc]init];
   traceConfig.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
    // 1. 获取业务自定义的 traceId
    NSString *replaceTrace = [request.allHTTPHeaderFields valueForKey:CUSTOM_TRACE_HEADER];

    // 2. 获取 SDK 标准 W3C traceparent 请求头
    NSDictionary *traceHeaders = [[FTExternalDataManager sharedManager] getTraceHeaderWithUrl:request.URL];
    NSString *traceParentStr = traceHeaders[FT_NETWORK_TRACEPARENT_KEY];

    // 3. 解析 W3C traceparent 格式并替换索引 1 的 traceId
    NSArray *traceComponents = [traceParentStr componentsSeparatedByString:@"-"];
    if (traceComponents.count != 4) {
        return nil;
    }
    NSMutableArray *newComponents = [traceComponents mutableCopy];
    newComponents[1] = replaceTrace;
    NSString *newTraceParent = [newComponents componentsJoinedByString:@"-"];

    // 4. 组装并返回自定义追踪上下文
    FTTraceContext *context = [FTTraceContext new];
    context.traceHeader = @{FT_NETWORK_TRACEPARENT_KEY:newTraceParent};
    context.traceId = replaceTrace;
    // 保留 SDK 生成的 spanId(索引 2 固定位置)
    context.spanId = newComponents[2];
    return context;
};
let traceConfig = FTTraceConfig()
traceConfig.traceInterceptor = { (request: URLRequest) -> FTTraceContext? in
        // 1. 获取业务自定义的 traceId
        guard let replaceTrace = request.allHTTPHeaderFields?[CUSTOM_TRACE_HEADER] else {
            return nil
        }

        // 2. 获取 SDK 标准 W3C traceparent 请求头
        guard let traceHeaders = FTExternalDataManager.shared().getTraceHeader(with: request.url!), let traceParentStr = traceHeaders[FT_NETWORK_TRACEPARENT_KEY] as? String else {
            return nil
        }

        // 3. 解析 W3C traceparent 格式并替换索引 1 的 traceId
        let traceComponents = traceParentStr.components(separatedBy: "-")
        guard traceComponents.count == 4 else {
            return nil
        }
        var newComponents = traceComponents
        newComponents[1] = replaceTrace
        let newTraceParent = newComponents.joined(separator: "-")

        // 4. 组装并返回自定义追踪上下文
        let context = FTTraceContext()
        context.traceHeader = [FT_NETWORK_TRACEPARENT_KEY: newTraceParent]
        context.traceId = replaceTrace
        // 保留 SDK 生成的 spanId(索引 2 固定位置)
        context.spanId = newComponents[2]
        return context
    }

文档评价

文档内容是否对您有帮助?