数据采集自定义规则¶
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。
如需自定义 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 采集过滤¶
添加自定义属性¶
通过设置属性提供程序闭包,您可以返回要附加到 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};
}
过滤网络错误¶
网络请求发生错误时,RUM 中会生成一条类型为 network_error 的 Error 数据,一些 URLSeesion 的本地错误比如 task.cancel 是用户程序中的正常逻辑,并非是错误数据,此时可以通过 sessionTaskErrorFilter 回调进行拦截过滤。确认拦截返回 YES,不拦截返回 NO,拦截后 RUM-Error 不采集该条错误。
Error¶
通过 FTRumConfig.issueDataProvider,可以在 SDK 自动采集 Crash 或 ANR 时,根据本次异常的信息为对应的 RUM Error 添加业务字段,例如业务场景、功能模块或实验分组。该能力在 SDK 1.6.7 及以上版本支持。
issueDataProvider 只为自动采集的 Crash 和 ANR 添加字段,不会开启 Crash 或 ANR 监控。使用前需要根据采集目标开启 enableTrackAppCrash 或 enableTrackAppANR。
配置¶
请在启动 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 |
自动采集场景中的 category、errorType 和 historical 对应关系如下:
| 场景 | category | errorType | historical |
|---|---|---|---|
| Crash 报告在应用下次启动时恢复 | Crash | ios_crash |
YES |
| 当前进程恢复后的 ANR | ANR | anr_error |
NO |
| 上一个进程的 Watchdog ANR 在应用下次启动时恢复 | ANR | anr_error |
YES |
historical 为 YES 时,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
}