应用性能监控全链路版的iOS SDK基本为无侵入式。本文介绍产品形态为SDK的详细的接入步骤。
注意
APMPlus_iOS
Demo中提供了各功能模块的子库,子库和模块的对应关系如下表所示:
子库 | 说明 | 对应平台模块 | 开始支持版本 |
---|---|---|---|
Crash | 崩溃监控:捕获CPP Exception、Mach Exception、NSException Exception 和 Signal Exception | 崩溃分析 | 2.8.1 |
WatchDog | 卡死监控:监控主线程长时间卡住被系统 watchdog 给强杀的情况 | 崩溃分析 | 2.8.1 |
UserException | 自定义错误,需要业务方手动打点 | 错误分析-自定义错误 | 2.8.1 |
EventMonitor | 事件分析,记录自定义事件,需要手动埋点 | 事件分析 | 2.8.1 |
SessionTracker | PV/UV统计,接入后会自动上报数据 | 各模块异常率、异常用户比例等 | 2.8.1 |
BootingProtectLite | 连续崩溃保护 | - | 2.10.0 |
APMLog | APM日志库,可以手动打点记录SDK运行日志 | 单点追查-回捞 | 3.5.3 |
CloudCommand | 回捞 | 单点追查-回捞 | 3.5.3 |
接入组件视角的监控能力,有两种方式:宿主直接依赖和组件依赖。
在宿主Podfile中添加如下代码。
source 'https://github.com/volcengine/volcengine-specs.git' pod 'RangersAPM', '3.6.4', :subspecs => [ 'Crash', 'WatchDog', 'UserException', 'EventMonitor', 'SessionTracker', 'CN' #必须引入 ]
执行pod install
或者pod update
,安装SDK。
在组件的podspec中添加如下代码。
Pod::Spec.new do |s| s.name = 'xxx' s.version = '0.0.1' s.summary = 'xxx' s.source_files = 'Classes/**/*' ... s.dependency 'RangersAPM/Crash', '3.3.2' s.dependency 'RangersAPM/WatchDog', '3.3.2' s.dependency 'RangersAPM/UserException', '3.3.2' s.dependency 'RangersAPM/EventMonitor', '3.3.2' s.dependency 'RangersAPM/SessionTracker', '3.3.2' s.dependency 'RangersAPM/CN', '3.3.2' end
按需引入子库,具体请参见Demo说明。
在组件的初始化代码中(或者在用户同意隐私政策之后的合适时机)添加如下代码。{{sdk_id}}
和{{app_token}}
须替换为您创建的应用对应的AppID和AppToken,具体请参见如何查询AppID和AppToken?。
#import <RangersAPMForSDK.h> - (void)init { RangersAPMForSDKConfig *sdkConfig = [RangersAPMForSDKConfig configWithSDKID:@"{{sdk_id}}" appToken:@"{{app_token}}"]; sdkConfig.channel = @"Cocoapods"; //SDK发布渠道,非必填 sdkConfig.hostAppID = @"host_app_id"; //宿主APP标识,非必填 sdkConfig.sdkVersion = @"1.0.0"; //SDK版本,必填 }
在组件的初始化代码中(或者在用户同意隐私政策之后的合适时机)添加如下代码,sdkConfig为初始化时生成的配置。
#import <RangersAPMForSDK.h> - (void)start { RangersAPMForSDK *sdkMonitor = [[RangersAPMForSDK alloc] initWithConfig:sdkConfig]; }
说明
建议启动代码调用时机应尽量靠前,上述代码调用之前发生的崩溃等数据无法捕获。
注意
组件监控的崩溃日志需要上传了符号表才能解析。如果您的SDK是静态库,符号表是接入宿主APP后,宿主APP打包产生的符号表。
请确保您的符号表满足如下格式。
把符号表压缩为zip文件。
Mac下zip需要执行以下命令,去除默认生成DS_Store \__MACOSX
文件。
zip -r test.app.dSYM.zip test.app.dSYM -x "*.DS_Store" -x "__MACOSX"
将test.app.dSYM.zip
和test.app.dSYM
替换为您的符号表名称。
上传符号表。
在符号表管理模块上传符号表。
使用curl命令上传。
curl https://console.volcengine.com/apmplus_api/eue/guest/app/mapping/upload -F "file=@dSYMZipName" -F "type=Dwarf" -F "os=iOS" -F "aid=APMPlusID" -H "Content-Type: multipart/form-data" -w %{http_code}
dSYMZipName
替换为您的符号表文件路径,APMPlusID
替换为您的AppID。在Xcode中对应Target下配置Build Phases、添加Run Script,可以实现APP打包时自动上传符号表。
说明
默认Debug模式和模拟器编译不会上传符号表。如果需要在这两种情况下上传符号表,请参见手动上传。
如果您接入的SDK版本大于1.5.0(包含),仅需要在脚本中添加如下代码 ,并将命令中的APMPlusID
替换为您的应用ID即可,这种方式在Debug模式和模拟器编译时不会上传符号表。
/bin/sh ${PODS_ROOT}/RangersAPM/RangersAPM/APMPlus_DSYMUploader.sh "APMPlusID"
如果您接入的SDK为较低版本且不想升级为更高版本,或者需要Debug模式和模拟器编译才能自动上传符号表。
APMPlus_APP_ID
为您的应用ID。UPLOAD_DEBUG_SYMBOLS
和UPLOAD_SIMULATOR_SYMBOLS
字段。您可以根据需要,按照以下各模块说明,检查对应模块是否接入成功。
前提条件
在SDK启动方法之前配置以下代码,否则一些同步事件可能无法输出日志。
#if DEBUG [RangersAPM allowDebugLogUsingLogger:^(NSString * _Nonnull log) { NSLog(@"APMPlus : %@", log); }]; #endif RangersAPMForSDK *sdkMonitor = [[RangersAPMForSDK alloc] initWithConfig:sdkConfig];
开启Debug日志
开启Debug输出功能后,SDK在关键事件发生(初始化成功,上报成功等)时会向Xcode控制台输出日志,帮助您对SDK的接入和上报进行验证。
示例代码:
#import <RangersAPM+DebugLog.h> [RangersAPM allowDebugLogUsingLogger:^(NSString * _Nonnull log) { NSLog(@"APMPlus : %@", log); }];
支持通过修改block自定义日志输出格式。
如果传入nil,SDK会使用内部默认的格式输出日志:
[RangersAPM allowDebugLogUsingLogger:nil];
通过测试用例可以验证SDK功能是否已正确开启,您可以在代码中添加测试用例。
如果您不了解如何添加测试用例,可以下载Example工程,或者参考下面各模块给出的样例代码。
debug日志
日志内容 | 说明 |
---|---|
Setup APMPlus - version : | SDK 初始化开始,准备启动各功能模块,同时输出当前版本 |
完整的崩溃分析功能需要引入如下子库:Crash、WatchDog,支持单独引入各个子库。
监控组件内部发生的崩溃,首先需要获取组件的地址区间,我们提供三种方案。
方案1:添加标记函数
这种方案接入方式简单,不需要宿主APP做额外配置,但是对组件有一定的侵入性,且二进制重排后地址区间的准确性无法保证。
在组件中添加如下两个源文件,并定义两个函数,把一个函数的地址作为组件的起始地址,另一个函数的地址作为组件的结束地址。
注意
不要直接用示例的文件名和函数名,需要添加前缀,避免冲突。
//SDKBegin.c #include "SDKBegin.h" extern void * SDKBeginAddress(void) { return &SDKBeginAddress; } //SDKEnd.c #include "SDKEnd.h" extern void * SDKEndAddress(void) { return &SDKEndAddress; }
在Xcode Build Phases - Compile Sources里面调整编译顺序。
如下所示,SDKBegin.c
为第一个编译,SDKEnd.c
为最后一个编译。
在RangersAPM SDK初始化时配置config的addressConfig参数,addressConfig支持传入多个地址区间,监控多段地址。
#import <RangersAPMForSDK.h> extern void * SDKBeginAddress; extern void * SDKEndAddress; -(void)init { NSArray * addressRanges = @[[RangersAddressRange addressRangeWithStartAddress:(int64_t)&SDKBeginAddress endAddress:(int64_t)&SDKEndAddress]]; RangersAPMAddressConfig * addressConfig = [RangersAPMAddressConfig configWithAddressRanges:addressRanges]; RangersAPMForSDKConfig *sdkConfig = [RangersAPMForSDKConfig configWithSDKID:@"{{app_id}}" appToken:@"{{app_token}}"]; sdkConfig.addressConfig = addressConfig; sdkConfig.channel = @"Cocoapods"; sdkConfig.hostAppID = @"host_app_id"; //宿主APP标识 sdkConfig.sdkVersion = @"1.0.0"; }
方案2:添加编译脚本
这种方案不需要在RangersAPM SDK初始化时做其他的配置,准确度比方案1更高,但是需要宿主配合在Xcode中添加脚本。
注意
添加的脚本是在宿主的Project中添加,而不是在组件的Project中。
下载脚本。
修改脚本内容。
SDKAid="123456" SDKName="RangersAPM" writeAddressRangeFile $SDKName $SDKAid
参数说明:
参数 | 说明 |
---|---|
SDKAid |
|
SDKName |
|
同时监控多个SDK时,需要注意:
如果这些SDK需要汇总到一个看板,即数据上报到APMPlus同一个AppID下面,则需要修改SDKName,并使用'|'把这些SDK name分隔开,即SDKName="SDK1|SDK2|SDK3"。
SDKAid="123456" SDKName="RangersAPM|RangersAppLog" writeAddressRangeFile $SDKName $SDKAid
如果这些SDK是相对独立的,即数据上报到各自的AppID下面,此时会有多个AppID,对每一个SDK进行上述调用。
SDKAid1="123456" SDKName1="RangersAPM" writeAddressRangeFile $SDKName1 $SDKAid1 SDKAid2="4567890" SDKName2="RangersAppLog" writeAddressRangeFile $SDKName2 $SDKAid2
在Xcode - Build Settings中把Write Link Map File置为YES,允许生成Link Map文件。
由于脚本执行有一定耗时,为了避免影响开发体验,建议仅在Release环境下修改Write Link Map File为YES。
把修改后的脚本粘贴到Run Script里面,完成脚本接入工作。
添加的Run Script需要位于Copy Pods Resources之前,Compile Sources之后。您可以通过拖动来移动Run Script的位置。
方案3:配置库名(仅适用于动态库)
在初始化应用性能监控全链路版SDK时,配置RangersAPMForSDKConfig的libNames属性,传入您的SDK包名。
#import <RangersAPMForSDK.h> -(void)init { RangersAPMForSDKConfig *sdkConfig = [RangersAPMForSDKConfig configWithSDKID:@"{{app_id}}" appToken:@"{{app_token}}"]; sdkConfig.libNames = @[@"SDK1", @"SDK2"]; sdkConfig.channel = @"Cocoapods"; sdkConfig.hostAppID = @"host_app_id"; //宿主APP标识 sdkConfig.sdkVersion = @"1.0.0"; }
下面的代码会触发NSException类型的Crash,更多类型的case,可以下载Example工程。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSArray *array = [NSArray array]; [array objectAtIndex:10]; });
注意
不要直接通过Xcode Run启动APP,这样触发的崩溃无法捕获。
日志内容 | 说明 |
---|---|
Crash-Monitor start successfully! | 崩溃监控模块启动成功 |
WatchDog-Monitor start successfully! | 卡死监控模块启动成功 |
Crash log is uploaded successfully! | 崩溃日志上报成功 |
Watchdog log is uploading... | 开始上传卡死日志 |
自定义错误模块需要引入子库UserException,自定义错误为自埋点功能,需要手动调用接口来记录APP发生的错误,并上报到火山引擎应用性能监控全链路版平台,统一查看。
下面的代码会记录一条自定义错误日志,SDK每记录5条日志会触发一次上报。网络错误日志可以通过发送一次会发生错误的网络请求来自动记录,可以参考Example工程。
#import <RangersAPMForSDK+UserException.h> //获取之前初始化的实例,需要先参考 初始化 章节初始化一个实例 RangersAPMForSDK *sdkMonitor = [RangersAPMForSDK monitorWithSDKID:@"{{sdk_id}}"]; [sdkMonitor trackAllThreadsLogExceptionType:@"testUserException" skippedDepth:0 customParams:@{@"testCustomKey":@"testCustomValue"} filters:@{@"testFilterKey":@"testFilterValue"} callback:^(NSError * _Nullable error){ NSLog(@"%@",error); }];
日志内容 | 说明 |
---|---|
UserException-Monitor start successfully! | 自定义错误监控模块启动成功 |
事件分析模块是一个自埋点功能,需要您手动调用接口来进行事件的记录,使用该功能需要引入EventMonitor模块。
可以通过如下代码记录一个事件,相关参数可以查看头文件介绍。
#import "RangersAPMForSDK+EventMonitor.h" //获取之前初始化的实例,需要先参考 初始化 章节初始化一个实例 RangersAPMForSDK *sdkMonitor = [RangersAPMForSDK monitorWithSDKID:@"{{app_id}}"]; [sdkMonitor trackEvent:@"event_name1" metrics:@{@"metric1":@(0)} dimension:@{@"dimension1":@"test"} extraValue:@{@"extra1":@"extravalue"}];
日志内容 | 说明 |
---|---|
Record an event-log successfully, name: | 成功记录一条事件日志,并输出事件名称 |
日志回捞模块需要引入子库APMLog。通过使用SDK提供的接口进行打点,可以记录一些APP运行期间产生的日志。此模块是一种基于mmap的高效率的日志打点框架,日志压缩率高达25倍,结合云控可以做到线上用户日志的实时定向回捞,帮您高效率,精准的定位、解决问题。
日志不会全部上报,获取这些日志的方式如下:
测试用例是通过在项目中添加样例代码并在合适的时机触发,来验证SDK能否捕获对应事件的日志。您可以参见各模块给出的样例代码和说明,或者参见Example工程。
对于C/C++、Objective-C、Swift,APMPlus提供了三类日志打点的接口,每一类有四个接口,分别为Debug、Info、Warn、Error,代表日志严重程度的四个等级,可以在平台查看日志时进行筛选。
//无论使用哪类接口,首先都需要先调用如下接口开启Alog功能 //注意:仅在APP启动时调用一次即可 #import "RangersAPMForSDK+ALog.h" RangersAPMForSDK *sdkMonitor = [[RangersAPMForSDK alloc] initWithConfig:sdkConfig]; [sdkMonitor setALogEnabled]; //启用Alog [sdkMonitor enableConsoleLog]; //同时在控制台输出日志 //Objective-C 可以使用如下接口进行日志打点 #import "RangersAPMForSDK+ALog.h" //第一个参数为初始化时使用的 SDKID //第二个参数标识当前日志的业务、场景等信息; //第三个参数为日志具体信息,可以使用format类型,如果使用format,需要继续传入对应的参数 RANGERSAPM_FORSDK_ALOG_DEBUG(sdkConfig.sdkID, @"Business", @"version : %@", [self version]); //Debug类日志 RANGERSAPM_FORSDK_ALOG_INFO(sdkConfig.sdkID, @"Business", @"version : %@", [self version]); // Info类日志 RANGERSAPM_FORSDK_ALOG_WARN(sdkConfig.sdkID, @"Business", @"version : %@", [self version]); //Warn类日志 RANGERSAPM_FORSDK_ALOG_ERROR(sdkConfig.sdkID, @"Business", @"version : %@", [self version]); //Error类日志 //C/C++ 可以使用如下接口进行日志打点 #import "RangersAPM_ForSDK_ALog.h" //第一个参数为初始化时使用的 SDKID //第二个参数标识当前日志的业务、场景等信息; //第三个参数为日志具体信息,可以使用format类型,如果使用format,需要继续传入对应的参数 RANGERSAPM_FORSDK_ALOG_DEBUG_C([sdkConfig.sdkID UTF8String], "Business", "version : %s", version()); RANGERSAPM_FORSDK_ALOG_INFO_C([sdkConfig.sdkID UTF8String], "Business", "version : %s", version()); RANGERSAPM_FORSDK_ALOG_WARN_C([sdkConfig.sdkID UTF8String], "Business", "version : %s", version()); RANGERSAPM_FORSDK_ALOG_ERROR_C([sdkConfig.sdkID UTF8String], "Business", "version : %s", version()); //Swift 可以使用如下接口进行日志打点 #import "RangersAPMForSDK+ALog.h" //第一个参数为日志具体信息 //tag 标识当前日志的业务、场景等信息; //fileName 为当前所在文件名,可以参考示例传入 #file //funcName 为当前所在方法名,可以参考示例传入 #function //line 为当前所在文件的行号,可以参考示例传入 #line sdkMonitor.debugALog("alogtest", tag: "Business", fileName: #file, funcName: #function, line: #line) sdkMonitor.infoALog("alogtest", tag: "Business", fileName: #file, funcName: #function, line: #line) sdkMonitor.warnALog("alogtest", tag: "Business", fileName: #file, funcName: #function, line: #line) sdkMonitor.errorALog("alogtest", tag: "Business", fileName: #file, funcName: #function, line: #line)
完成开启Debug日志后,根据输出日志验证模块是否接入成功。
日志内容 | 说明 |
---|---|
ALog is uploaded successfully! | (回捞)自定义日志上报成功 |
Upload ALog failed, reason : | (回捞)自定义日志上报失败,并输出原因 |
ALog is uploaded manually! | 手动上报自定义日志成功 |
Manually upload ALog failed | 手动上报自定义日志失败 |
CloudCommand start successfully! | 回捞功能启动成功 |
CloudCommand is being executed ... | 回捞指令正在执行 |
ALog start successfully! | 自定义日志功能启动成功 |
除了崩溃分析、错误分析、卡死分析等常用的功能外,SDK还支持一些增强功能。您可以根据业务需求,引入相应子库后使用这些功能。
使用如下接口,您可以添加一些自定义的环境或业务信息,如用户email、定位信息、业务场景等,这些信息会随着SDK的日志一同上报,帮助您排查一些问题,您可以在日志详情页查看这些信息。
#import <RangersAPMForSDK.h> //获取之前初始化的实例,需要先参考 初始化 章节初始化一个实例 RangersAPMForSDK *sdkMonitor = [RangersAPMForSDK monitorWithSDKID:@"{{app_id}}"]; //自定义key-value [sdkMonitor setCustomContextValue:@"sdk1context1value" forKey:@"sdk1context1key"];
除了上报自定义数据外,您也可以在需要的时候添加一些自定义维度,作为自定义的的筛选项,用来筛选某些特定情况下的日志。
#import <RangersAPMForSDK.h> //获取之前初始化的实例,需要先参考 初始化 章节初始化一个实例 RangersAPMForSDK *sdkMonitor = [RangersAPMForSDK monitorWithSDKID:@"{{app_id}}"]; //自定义筛选项 [sdkMonitor setCustomFilterValue:@"sdk1filter1value" forKey:@"sdk1filter1key"];
连续崩溃保护需要接入SDK的BootingProtectLite模块,具体请参见Demo说明。使用此功能,您可以在应用发生连续的崩溃时进行一些本地的处理,例如清理缓存、删除文件等。
#import <RangersAPMForSDK+BootingProtect.h> //获取之前初始化的实例,需要先参考 初始化 章节初始化一个实例 RangersAPMForSDK *sdkMonitor = [RangersAPMForSDK monitorWithSDKID:@"{{app_id}}"]; [sdkMonitor startProtectWithBootingThreshold:10 bootingCrashHandler:^(RangersAPMBootingInfo * _Nonnull info) { /** 对连续异常的场景进行防护策略,这里的demo只输出了一些log,您可以在自己的应用中做一些本地缓存清理或其他策略。可以针对不同的异常发生次数制定不同的策略。 */ if (info.consecutiveExceptionTimes >= 1) { NSLog(@"⚠️Consecutive exception 1 time"); } else if (info.consecutiveExceptionTimes >= 3) { NSAssert(NO, @"⚠️Consecutive exception 3 times !!!"); } /** 除了对连续异常进行防护,您也可以针对不同的异常类型执行不同的防护策略。 */ if (info.crashTimes >= 1) { NSLog(@"⚠️Consecutive crash 1 time"); } if (info.watchdogTimes >= 1) { NSLog(@"⚠️Consecutive watchdog 1 time"); } }];
说明
如果您要使用自己的deviceID,则SDK内部不会再次请求自身服务生成deviceID。
#import <RangersAPMForSDK.h> - (void)init { RangersAPMForSDKConfig *sdkConfig = [RangersAPMForSDKConfig configWithSDKID:@"{{app_id}}" appToken:@"{{app_token}}"]; sdkConfig.channel = @"Cocoapods"; sdkConfig.hostAppID = @"host_app_id"; //宿主APP标识 sdkConfig.sdkVersion = @"1.0.0"; sdkConfig.deviceIDSource = RAPMDeviceIDSourceFromUser; RangersAPMForSDK *sdkMonitor = [[RangersAPMForSDK alloc] initWithConfig:sdkConfig]; [sdkMonitor setDeviceID:@"MYDEVICEID"]; }