iOS/tvOS Application Integration¶
By collecting metric data from various iOS applications, the performance of each iOS application can be analyzed in a visual manner.
Prerequisites¶
Note
If RUM Headless service has already been activated, the prerequisites have been automatically configured. You can proceed directly to integrate your application.
- Install DataKit;
- Configure the RUM Collector;
- Set DataKit to be publicly accessible and install the IP geolocation database: refer to this guide.
Application Integration¶
- Navigate to User Access Monitoring > Create Application > iOS;
- Enter an application name;
- Enter an application ID;
-
Choose an integration method:
- Public DataWay: Directly receives RUM data without requiring installation of the DataKit collector.
- Local Environment Deployment: Receives RUM data after fulfilling prerequisites.
Installation¶
Source Code Repository: https://github.com/GuanceCloud/datakit-ios
Demo: https://github.com/GuanceDemo/guance-app-demo
-
Configure the
Podfile
file.-
Using Dynamic Library
-
Using Static Library
Podfile
:use_modular_headers! # Main project target 'yourProjectName' do pod 'FTMobileSDK', :path => '[folder_path]' end # Widget Extension target 'yourWidgetExtensionName' do pod 'FTMobileSDK', :subspecs => ['Extension'] , :path => '[folder_path]' end
folder_path
: Path to the folder containingFTMobileSDK.podspec
.FTMobileSDK.podspec
file:Modify the
s.version
ands.source
fields in theFTMobileSDK.podspec
file.Pod::Spec.new do |s| s.name = "FTMobileSDK" s.version = "[latest_version]" s.source = { :git => "https://github.com/GuanceCloud/datakit-ios.git", :tag => s.version } end
s.version
: Change to the specified version. It is recommended that this be consistent with theSDK_VERSION
inFTMobileSDK/FTMobileAgent/Core/FTMobileAgentVersion.h
.s.source
:tag => s.version
-
-
Execute
pod install
in the directory containing thePodfile
to install the SDK.
-
Configure the
Cartfile
file. -
Update dependencies.
Depending on your target platform (iOS or tvOS), execute the appropriate
carthage update
command and add the--use-xcframeworks
parameter to generate XCFrameworks:-
For iOS platform:
-
For tvOS platform:
The generated xcframework can be used similarly to ordinary frameworks. Add the compiled libraries to your project.
FTMobileAgent
: Add to the main project Target; supports both iOS and tvOS platforms.FTMobileExtension
: Add to the widget Extension Target. -
-
In
TARGETS
->Build Setting
->Other Linker Flags
, add-ObjC
. -
When integrating via Carthage, supported SDK versions are:
FTMobileAgent
: >=1.3.4-beta.2FTMobileExtension
: >=1.4.0-beta.1
-
Select
PROJECT
->Package Dependency
, then click the + under thePackages
section. -
In the search field on the pop-up page, enter
https://github.com/GuanceCloud/datakit-ios.git
. -
After Xcode successfully fetches the package, it will display the SDK configuration page.
Dependency Rule
: It is recommended to selectUp to Next Major Version
.Add To Project
: Select the project(s) that should support the SDK.Fill in the configuration details and click the
Add Package
button, waiting until loading completes. -
In the pop-up window titled
Choose Package Products for datakit-ios
, select the Target(s) where you want to add the SDK, then click theAdd Package
button. At this point, the SDK has been successfully added.FTMobileSDK
: Added to the main project TargetFTMobileExtension
: Added to the Widget Extension TargetIf your project is managed by SPM, add the SDK as a dependency by adding the
dependencies
toPackage.swift
. -
Swift Package Manager is supported starting from version 1.4.0-beta.1.
Adding Header Files¶
SDK Initialization¶
Basic Configuration¶
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
// SDK FTMobileConfig Settings
// Local environment deployment, Datakit deployment
//FTMobileConfig *config = [[FTMobileConfig alloc]initWithDatakitUrl:datakitUrl];
// Using public DataWay deployment
FTMobileConfig *config = [[FTMobileConfig alloc]initWithDatawayUrl:datawayUrl clientToken:clientToken];
//config.enableSDKDebugLog = YES; // Debug mode
config.compressIntakeRequests = YES;
// Start SDK
[FTMobileAgent startWithConfigOptions:config];
//...
return YES;
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// SDK FTMobileConfig Settings
// Local environment deployment, Datakit deployment
//let config = FTMobileConfig(datakitUrl: url)
// Using public DataWay deployment
let config = FTMobileConfig(datawayUrl: datawayUrl, clientToken: clientToken)
//config.enableSDKDebugLog = true // Debug mode
config.compressIntakeRequests = true // Compress reporting data
FTMobileAgent.start(withConfigOptions: config)
//...
return true
}
Property | Type | Required | Description |
---|---|---|---|
datakitUrl | NSString | Yes | URL address for local environment deployment (Datakit) reporting. Example: http://10.0.0.1:9529. Default port is 9529. Devices installing the SDK must be able to access this address. Note: Only one of datakitUrl and datawayUrl should be configured |
datawayUrl | NSString | Yes | URL address for public Dataway reporting. Obtain from the [User Access Monitoring] application. Example: https://open.dataway.url. Devices installing the SDK must be able to access this address. Note: Only one of datakitUrl and datawayUrl should be configured |
clientToken | NSString | Yes | Authentication token, needs to be used together with datawayUrl |
enableSDKDebugLog | BOOL | No | Determines if logs should be printed. Default NO |
env | NSString | No | Sets the collection environment. Default prod . Custom values are allowed. Alternatively, use the provided FTEnv enum via the -setEnvWithType: method |
service | NSString | No | Sets the name of the business or service it belongs to. This affects the service field data in Logs and RUM. Default: df_rum_ios |
globalContext | NSDictionary | No | Adds custom tags. Refer to here for rules on adding |
groupIdentifiers | NSArray | No | Array of AppGroups Identifiers for Widget Extensions whose data needs to be collected. If Widget Extensions data collection is enabled, configure the App Groups as described here and set the Identifier in this property |
autoSync | BOOL | No | Determines whether data should be automatically synced to the server after collection. Default YES . When set to NO , manage data synchronization manually using [[FTMobileAgent sharedInstance] flushSyncData] |
syncPageSize | int | No | Sets the number of entries per sync request. Range [5,). Note: Larger request sizes consume more computing resources. Default is 10 |
syncSleepTime | int | No | Sets the interval between sync operations. Range [0,5000]. Default is not set |
enableDataIntegerCompatible | BOOL | No | Recommended to enable if coexisting with web data. This setting handles compatibility issues related to web data type storage. |
compressIntakeRequests | BOOL | No | Compresses uploaded sync data using deflate compression. Supported in SDK version 1.5.6 and above. Default is off |
enableLimitWithDbSize | BOOL | No | Enables limiting the total cache size using DB. Note: Enabling this makes FTLoggerConfig.logCacheLimitCount and FTRUMConfig.rumCacheLimitCount ineffective. Supported in SDK version 1.5.8 and above |
dbCacheLimit | long | No | DB cache limit size. Range [30MB,). Default is 100MB, unit byte. Supported in SDK version 1.5.8 and above |
dbDiscardType | FTDBCacheDiscard | No | Sets the rule for discarding data in the database. Default FTDBDiscard FTDBDiscard Discards appended data when the data count exceeds the maximum value. FTDBDiscardOldest Discards old data when the data count exceeds the maximum value. Supported in SDK version 1.5.8 and above |
dataModifier | FTDataModifier | No | Modifies individual fields. Supported in SDK version 1.5.16 and above. See here for usage examples |
lineDataModifier | FTLineDataModifier | No | Modifies entire data lines. Supported in SDK version 1.5.16 and above. See here for usage examples |
remoteConfiguration | BOOL | No | Determines if dynamic configuration for data collection should be enabled. Disabled by default. Enabling triggers data updates during SDK initialization or hot app startup. Supported in SDK version 1.5.17 and above. Requires Datakit version >=1.60 or use of public Dataway |
remoteConfigMiniUpdateInterval | int | No | Sets the minimum update interval for dynamic configuration updates, in seconds. Default is 12 hours. Supported in SDK version 1.5.17 and above |
RUM Configuration¶
// Enable rum
FTRumConfig *rumConfig = [[FTRumConfig alloc]initWithAppid:appid];
rumConfig.enableTraceUserView = YES;
rumConfig.deviceMetricsMonitorType = FTDeviceMetricsMonitorAll;
rumConfig.monitorFrequency = FTMonitorFrequencyRare;
rumConfig.enableTraceUserAction = YES;
rumConfig.enableTraceUserResource = YES;
rumConfig.enableTrackAppFreeze = YES;
rumConfig.enableTrackAppCrash = YES;
rumConfig.enableTrackAppANR = YES;
rumConfig.errorMonitorType = FTErrorMonitorAll;
[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];
let rumConfig = FTRumConfig(appid: appid)
rumConfig.enableTraceUserView = true
rumConfig.deviceMetricsMonitorType = .all
rumConfig.monitorFrequency = .rare
rumConfig.enableTraceUserAction = true
rumConfig.enableTraceUserResource = true
rumConfig.enableTrackAppFreeze = true
rumConfig.enableTrackAppCrash = true
rumConfig.enableTrackAppANR = true
rumConfig.errorMonitorType = .all
FTMobileAgent.sharedInstance().startRum(withConfigOptions: rumConfig)
Property | Type | Required | Description |
---|---|---|---|
appid | NSString | Yes | Unique identifier for the User Access Monitoring application. This sets the RUM appid and enables RUM collection functionality. How to get appid |
samplerate | int | No | Sampling rate. Value range [0,100], 0 means no collection, 100 means full collection. Default value is 100. Scope applies to all View, Action, LongTask, Error data under the same session_id |
sessionOnErrorSampleRate | int | No | Sets error sampling rate. If the session was not sampled by samplerate , data within the first minute before the error occurred can be collected. Value range [0,100], 0 means no collection, 100 means full collection. Default value is 0. Scope applies to all View, Action, LongTask, Error data under the same session_id. Supported in SDK version 1.5.16 and above |
enableTrackAppCrash | BOOL | No | Determines if crash logs should be collected. Default NO |
enableTrackAppANR | BOOL | No | Collects ANR stall events. Default NO |
enableTrackAppFreeze | BOOL | No | Collects UI stall events. Default NO You can enable collection of stalls and set the threshold by calling -setEnableTrackAppFreeze:freezeDurationMs: |
freezeDurationMs | long | No | Sets the UI stall threshold. Value range [100,), unit milliseconds. Default is 250ms. Supported in SDK version 1.5.7 and above |
enableTraceUserView | BOOL | No | Determines if user View operations should be tracked. Default NO |
enableTraceUserAction | BOOL | No | Determines if user Action operations should be tracked. Default NO |
enableTraceUserResource | BOOL | No | Determines if user network requests should be tracked. Default NO , only affects native HTTP Note: Network requests initiated via [NSURLSession sharedSession] cannot collect performance data;Supports collecting network requests initiated via Swift's URLSession async/await APIs in SDK version 1.5.9 and above. |
resourceUrlHandler | FTResourceUrlHandler | No | Customizes resource collection rules. Default is no filtering. Returns: NO means collect, YES means do not collect. |
errorMonitorType | FTErrorMonitorType | No | Enhances error event monitoring with additional types. Adds monitoring information to collected crash data. FTErrorMonitorBattery for battery level, FTErrorMonitorMemory for memory usage, FTErrorMonitorCpu for CPU utilization. Default is not set. |
deviceMetricsMonitorType | FTDeviceMetricsMonitorType | No | Performance monitoring type for views. Adds corresponding monitoring item information to collected View data. FTDeviceMetricsMonitorMemory monitors current application memory usage, FTDeviceMetricsMonitorCpu monitors CPU pulse count, FTDeviceMetricsMonitorFps monitors screen frame rate. Default is not set. |
monitorFrequency | FTMonitorFrequency | No | Performance monitoring sampling cycle for views. Configures monitorFrequency to set the sampling cycle for View monitoring items. FTMonitorFrequencyDefault 500ms (default), FTMonitorFrequencyFrequent 100ms, FTMonitorFrequencyRare 1000ms. |
enableResourceHostIP | BOOL | No | Whether to collect the IP address of the target domain for requests. Supported on >= iOS 13.0 and >= tvOS 13.0 |
globalContext | NSDictionary | No | Adds custom tags to distinguish data sources for user monitoring. If tracking functionality is required, the parameter key should be track_id and value can be any value. Please review the addition rules at here |
rumCacheLimitCount | int | No | Maximum RUM cache capacity. Default is 100_000. Supported in SDK version 1.5.8 and above |
rumDiscardType | FTRUMCacheDiscard | No | Sets the RUM discard rule. Default FTRUMCacheDiscard FTRUMCacheDiscard Discards appended data when RUM data count exceeds maximum. FTRUMDiscardOldest Discards old data when RUM data count exceeds maximum. Supported in SDK version 1.5.8 and above |
resourcePropertyProvider | FTResourcePropertyProvider | No | Adds custom properties to RUM Resource via block callback. Supported in SDK version 1.5.10 and above. Lower priority than URLSession custom collection |
enableTraceWebView | BOOL | No | Determines if WebView data collection should be enabled. Default YES. Supported in SDK version 1.5.17 and above |
allowWebViewHost | NSArray | No | Sets hosts for which WebView data tracking is allowed. Nil means all hosts are tracked. Default is nil. Supported in SDK version 1.5.17 and above |
sessionTaskErrorFilter | FTSessionTaskErrorFilter | No | Sets whether to intercept SessionTask errors. Return YES to intercept, NO to not intercept. Intercepted errors won't be collected in RUM-Error. Supported in SDK version 1.5.17 and above |
Log Configuration¶
// Enable logger
FTLoggerConfig *loggerConfig = [[FTLoggerConfig alloc]init];
loggerConfig.enableCustomLog = YES;
loggerConfig.enableLinkRumData = YES;
loggerConfig.logLevelFilter = @[@(FTStatusError),@(FTStatusCritical)];
loggerConfig.discardType = FTDiscardOldest;
[[FTMobileAgent sharedInstance] startLoggerWithConfigOptions:loggerConfig];
let loggerConfig = FTLoggerConfig()
loggerConfig.enableCustomLog = true
loggerConfig.enableLinkRumData = true
loggerConfig.logLevelFilter = [NSNumber(value: FTLogStatus.statusError.rawValue),NSNumber(value: FTLogStatus.statusCritical.rawValue)] // loggerConfig.logLevelFilter = [2,3]
loggerConfig.discardType = .discardOldest
FTMobileAgent.sharedInstance().startLogger(withConfigOptions: loggerConfig)
Property | Type | Required | Description |
---|---|---|---|
samplerate | int | No | Sampling rate. Value range [0,100], 0 means no collection, 100 means full collection. Default is 100. |
enableCustomLog | BOOL | No | Whether to upload custom logs. Default NO |
printCustomLogToConsole | BOOL | No | Determines whether to output custom logs to the console. Default NO . Output format for custom logs is shown below |
logLevelFilter | NSArray | No | Sets level-based log filtering. Default is not set. Examples: 1. To collect Info and Error level logs, set to @[@(FTStatusInfo),@(FTStatusError)] or @[@0,@1] 2. To collect custom levels like "customLevel" and Error, set to @[@"customLevel",@(FTStatusError)] Filtering custom levels is supported in SDK version 1.5.16 and above |
enableLinkRumData | BOOL | No | Whether to associate with RUM data. Default NO |
globalContext | NSDictionary | No | Adds custom tags to logs. Refer to here for rules on adding |
logCacheLimitCount | int | No | Limits the maximum number of cached log entries [1000,). Larger numbers mean higher disk caching pressure. Default is 5000 |
discardType | FTLogCacheDiscard | No | Sets the discard rule when log count reaches the upper limit. Default FTDiscard FTDiscard Discards appended data when log count exceeds maximum (5000). FTDiscardOldest Discards old data when log count exceeds maximum. |
Trace Configuration¶
Property | Type | Required | Description |
---|---|---|---|
samplerate | int | No | Sampling rate. Value range [0,100], 0 means no collection, 100 means full collection. Default is 100. |
networkTraceType | FTNetworkTraceType | No | Sets the type of link tracing. Default is DDTrace . Currently supports Zipkin , Jaeger , DDTrace , Skywalking (8.0+), TraceParent (W3C). If integrating with OpenTelemetry, ensure the correct type and agent configuration are referenced |
enableLinkRumData | BOOL | No | Determines whether to associate with RUM data. Default NO |
enableAutoTrace | BOOL | No | Determines whether automatic HTTP tracing is enabled. Default NO . Currently only supports NSURLSession |
traceInterceptor | FTTraceInterceptor | No | Supports custom link tracing through URLRequest judgment. Return TraceContext to intercept, return nil to not intercept. Supported in SDK version 1.5.10 and above. Priority lower than URLSession custom collection |
RUM User Data Tracking¶
Configure FTRUMConfig
with enableTraceUserAction
, enableTraceUserView
, enableTraceUserResource
, enableTrackAppFreeze
, enableTrackAppCrash
, and enableTrackAppANR
to achieve automatic collection of Action, View, Resource, LongTask, Error data. If you want to perform custom collection, you can report data via FTExternalDataManager
, as shown in the example below:
View¶
Usage¶
/// Create page
///
/// Call before `-startViewWithName` method. This method records the page load time. If load time cannot be obtained, this method may be omitted.
/// - Parameters:
/// - viewName: Page name
/// - loadTime: Page load time (in nanoseconds)
-(void)onCreateView:(NSString *)viewName loadTime:(NSNumber *)loadTime;
/// Enter page
/// - Parameters:
/// - viewName: Page name
/// - property: Event custom attributes (optional)
-(void)startViewWithName:(NSString *)viewName property:(nullable NSDictionary *)property;
/// Leave page
/// - Parameter property: Event custom attributes (optional)
-(void)stopViewWithProperty:(nullable NSDictionary *)property;
/// Create page
///
/// Call before `-startViewWithName` method. This method records the page load time. If load time cannot be obtained, this method may be omitted.
/// - Parameters:
/// - viewName: Page name
/// - loadTime: Page load time (ns)
open func onCreateView(_ viewName: String, loadTime: NSNumber)
/// Enter page
/// - Parameters:
/// - viewName: Page name
/// - property: Event custom attributes (optional)
open func startView(withName viewName: String, property: [AnyHashable : Any]?)
/// Leave page
/// - Parameter property: Event custom attributes (optional)
open func stopView(withProperty property: [AnyHashable : Any]?)
Code Example¶
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Scenario 1:
[[FTExternalDataManager sharedManager] startViewWithName:@"TestVC"];
// Scenario 2:Dynamic parameters
[[FTExternalDataManager sharedManager] startViewWithName:@"TestVC" property:@{@"custom_key":@"custom_value"}];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
// Scenario 1:
[[FTExternalDataManager sharedManager] stopView];
// Scenario 2:Dynamic parameters
[[FTExternalDataManager sharedManager] stopViewWithProperty:@{@"custom_key":@"custom_value"}];
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Scenario 1:
FTExternalDataManager.shared().startView(withName: "TestVC")
// Scenario 2:Dynamic parameters
FTExternalDataManager.shared().startView(withName: "TestVC",property: ["custom_key":"custom_value"])
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Scenario 1:
FTExternalDataManager.shared().stopView()
// Scenario 2:Dynamic parameters
FTExternalDataManager.shared().stopView(withProperty: ["custom_key":"custom_value"])
}
Action¶
Usage¶
/// Start RUM Action.
///
/// RUM binds possible Resource, Error, LongTask events triggered by this Action. Avoid multiple additions within 0.1 s. Within the same time, only one Action can be associated with a single View. New Actions will be discarded if the previous Action hasn't finished.
/// Not affected by `addAction:actionType:property` method.
///
/// - Parameters:
/// - actionName: Event name
/// - actionType: Event type
/// - property: Event custom attribute (optional)
- (void)startAction:(NSString *)actionName actionType:(NSString *)actionType property:(nullable NSDictionary *)property;
/// Add Action event. No duration, no discard logic
///
/// Not affected by `startAction:actionType:property:` method.
/// - Parameters:
/// - actionName: Event name
/// - actionType: Event type
/// - property: Event custom attribute (optional)
- (void)addAction:(NSString *)actionName actionType:(NSString *)actionType property:(nullable NSDictionary *)property;
/// Start RUM Action.
///
/// RUM binds possible Resource, Error, LongTask events triggered by this Action. Avoid multiple additions within 0.1 s. Within the same time, only one Action can be associated with a single View. New Actions will be discarded if the previous Action hasn't finished.
/// Not affected by `addAction:actionType:property` method.
///
/// - Parameters:
/// - actionName: Event name
/// - actionType: Event type
/// - property: Event custom attribute (optional)
open func startAction(_ actionName: String, actionType: String, property: [AnyHashable : Any]?)
/// Add Action event. No duration, no discard logic
///
/// Not affected by `startAction:actionType:property:` method.
/// - Parameters:
/// - actionName: Event name
/// - actionType: Event type
/// - property: Event custom attribute (optional)
open func addAction(_ actionName: String, actionType: String, property: [AnyHashable : Any]?)
Code Example¶
Error¶
Usage¶
/// Add Error event
/// - Parameters:
/// - type: error type
/// - message: error message
/// - stack: stack info
/// - property: event custom attribute (optional)
- (void)addErrorWithType:(NSString *)type message:(NSString *)message stack:(NSString *)stack property:(nullable NSDictionary *)property;
/// Add Error event
/// - Parameters:
/// - type: error type
/// - state: program runtime status
/// - message: error message
/// - stack: stack info
/// - property: event custom attribute (optional)
- (void)addErrorWithType:(NSString *)type state:(FTAppState)state message:(NSString *)message stack:(NSString *)stack property:(nullable NSDictionary *)property;
/// Add Error event
/// - Parameters:
/// - type: error type
/// - message: error message
/// - stack: stack info
/// - property: event custom attribute (optional)
open func addError(withType: String, message: String, stack: String, property: [AnyHashable : Any]?)
/// Add Error event
/// - Parameters:
/// - type: error type
/// - state: program runtime status
/// - message: error message
/// - stack: stack info
/// - property: event custom attribute (optional)
open func addError(withType type: String, state: FTAppState, message: String, stack: String, property: [AnyHashable : Any]?)
Code Example¶
// Scenario 1
[[FTExternalDataManager sharedManager] addErrorWithType:@"type" message:@"message" stack:@"stack"];
// Scenario 2: Dynamic parameters
[[FTExternalDataManager sharedManager] addErrorWithType:@"ios_crash" message:@"crash_message" stack:@"crash_stack" property:@{@"custom_key":@"custom_value"}];
// Scenario 3: Dynamic parameters
[[FTExternalDataManager sharedManager] addErrorWithType:@"ios_crash" state:FTAppStateUnknown message:@"crash_message" stack:@"crash_stack" property:@{@"custom_key":@"custom_value"}];
// Scenario 1
FTExternalDataManager.shared().addError(withType: "custom_type", message: "custom_message", stack: "custom_stack")
// Scenario 2: Dynamic parameters
FTExternalDataManager.shared().addError(withType: "custom_type", message: "custom_message", stack: "custom_stack",property: ["custom_key":"custom_value"])
// Scenario 3: Dynamic parameters
FTExternalDataManager.shared().addError(withType: "custom_type", state: .unknown, message: "custom_message", stack: "custom_stack", property: ["custom_key":"custom_value"])
LongTask¶
Usage¶
Code Example¶
Resource¶
Usage¶
/// HTTP Request Started
/// - Parameters:
/// - key: Request identifier
/// - property: Event custom attribute (optional)
- (void)startResourceWithKey:(NSString *)key property:(nullable NSDictionary *)property;
/// Add Request Data
///
/// - Parameters:
/// - key: Request identifier
/// - metrics: Request-related performance attributes
/// - content: Request-related data
- (void)addResourceWithKey:(NSString *)key metrics:(nullable FTResourceMetricsModel *)metrics content:(FTResourceContentModel *)content;
/// HTTP Request Completed
/// - Parameters:
/// - key: Request identifier
/// - property: Event custom attribute (optional)
- (void)stopResourceWithKey:(NSString *)key property:(nullable NSDictionary *)property;
/// HTTP Request Started
/// - Parameters:
/// - key: Request identifier
/// - property: Event custom attribute (optional)
open func startResource(withKey key: String, property: [AnyHashable : Any]?)
/// HTTP Request Completed
/// - Parameters:
/// - key: Request identifier
/// - property: Event custom attribute (optional)
open func stopResource(withKey key: String, property: [AnyHashable : Any]?)
/// Add Request Data
///
/// - Parameters:
/// - key: Request identifier
/// - metrics: Request-related performance attributes
/// - content: Request-related data
open func addResource(withKey key: String, metrics: FTResourceMetricsModel?, content: FTResourceContentModel)
Code Example¶
// Step 1: Before request starts
[[FTExternalDataManager sharedManager] startResourceWithKey:key];
// Step 2: Request completed
[[FTExternalDataManager sharedManager] stopResourceWithKey:key];
// Step 3: Assemble Resource data
// FTResourceContentModel data
FTResourceContentModel *content = [[FTResourceContentModel alloc]init];
content.httpMethod = request.HTTPMethod;
content.requestHeader = request.allHTTPHeaderFields;
content.responseHeader = httpResponse.allHeaderFields;
content.httpStatusCode = httpResponse.statusCode;
content.responseBody = responseBody;
// iOS native
content.error = error;
// If time data for each stage can be obtained
// FTResourceMetricsModel
// For iOS native, use NSURLSessionTaskMetrics data directly with FTResourceMetricsModel's initialization method
FTResourceMetricsModel *metricsModel = [[FTResourceMetricsModel alloc]initWithTaskMetrics:metrics];
// Other platforms, all time data is in nanoseconds
FTResourceMetricsModel *metricsModel = [[FTResourceMetricsModel alloc]init];
// Step 4: Add resource, pass nil if no time data
[[FTExternalDataManager sharedManager] addResourceWithKey:key metrics:metricsModel content:content];
// Step 1: Before request starts
FTExternalDataManager.shared().startResource(withKey: key)
// Step 2: Request completed
FTExternalDataManager.shared().stopResource(withKey: resource.key)
// Step 3: ① Assemble Resource data
let contentModel = FTResourceContentModel(request: task.currentRequest!, response: task.response as? HTTPURLResponse, data: resource.data, error: error)
// ② If time data for each stage can be obtained
// FTResourceMetricsModel
// For iOS native, use NSURLSessionTaskMetrics data directly with FTResourceMetricsModel's initialization method
var metricsModel:FTResourceMetricsModel?
if let metrics = resource.metrics {
metricsModel = FTResourceMetricsModel(taskMetrics:metrics)
}
// Other platforms, all time data is in nanoseconds
metricsModel = FTResourceMetricsModel()
...
// Step 4: Add resource, pass nil if no time data
FTExternalDataManager.shared().addResource(withKey: resource.key, metrics: metricsModel, content: contentModel)
Logger Logging¶
When initializing the SDK Log Configuration, configure enableCustomLog
to allow adding custom logs.
Currently, log content is limited to 30 KB, and any excess characters will be truncated.
Usage¶
//
// FTLogger.h
// FTMobileSDK
/// Add info-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
-(void)info:(NSString *)content property:(nullable NSDictionary *)property;
/// Add warning-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
-(void)warning:(NSString *)content property:(nullable NSDictionary *)property;
/// Add error-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
-(void)error:(NSString *)content property:(nullable NSDictionary *)property;
/// Add critical-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
-(void)critical:(NSString *)content property:(nullable NSDictionary *)property;
/// Add ok-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
-(void)ok:(NSString *)content property:(nullable NSDictionary *)property;
/// Add custom log
/// - Parameters:
/// - content: Log content
/// - status: Log status level
/// - property: Custom attribute (optional)
- (void)log:(NSString *)content status:(NSString *)status property:(nullable NSDictionary *)property;
open class FTLogger : NSObject, FTLoggerProtocol {}
public protocol FTLoggerProtocol : NSObjectProtocol {
/// Add info-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
optional func info(_ content: String, property: [AnyHashable : Any]?)
/// Add warning-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
optional func warning(_ content: String, property: [AnyHashable : Any]?)
/// Add error-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
optional func error(_ content: String, property: [AnyHashable : Any]?)
/// Add critical-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
optional func critical(_ content: String, property: [AnyHashable : Any]?)
/// Add ok-level custom log
/// - Parameters:
/// - content: Log content
/// - property: Custom attribute (optional)
optional func ok(_ content: String, property: [AnyHashable : Any]?)
/// Add custom log
/// - Parameters:
/// - content: Log content
/// - status: Log status level
/// - property: Custom attribute (optional)
optional func log(_ content: String, status: String, property: [AnyHashable : Any]?)
}
Log Levels¶
Code Example¶
Custom Log Output to Console¶
Set printCustomLogToConsole = YES
to enable outputting custom logs to the console. You will see logs in the following format in the xcode debug console:
2023-06-29 13:47:56.960021+0800 App[64731:44595791]
: Standard prefix for os_log;
[IOS APP]
: Prefix to distinguish SDK output custom logs;
[INFO]
: Severity level of the custom log;
content
: Content of the custom log;
{K=V,...,Kn=Vn}
: Custom attributes.
Trace Network Tracing¶
You can configure automatic mode using FTTraceConfig
, or users can manually add Trace-related data. Relevant API for manual addition is as follows:
NSString *key = [[NSUUID UUID]UUIDString];
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
// Manual step needed: Get traceHeader before request and add to request header
NSDictionary *traceHeader = [[FTTraceManager sharedInstance] getTraceHeaderWithKey:key url:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
if (traceHeader && traceHeader.allKeys.count>0) {
[traceHeader enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
[request setValue:value forHTTPHeaderField:field];
}];
}
NSURLSession *session=[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// Your code
}];
[task resume];
let url:URL = NSURL.init(string: "https://www.baidu.com")! as URL
if let traceHeader = FTExternalDataManager.shared().getTraceHeader(withKey: NSUUID().uuidString, url: url) {
let request = NSMutableURLRequest(url: url)
// Manual step needed: Get traceHeader before request and add to request header
for (a,b) in traceHeader {
request.setValue(b as? String, forHTTPHeaderField: a as! String)
}
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
// Your code
}
task.resume()
}
Custom Collection of Network Through Forwarding URLSession Delegate¶
The SDK provides a class FTURLSessionDelegate
, which allows custom RUM Resource collection and distributed tracing for network requests initiated by a URLSession.
FTURLSessionDelegate
supports interceptingURLResquest
through thetraceInterceptor
block for custom distributed tracing (supported from SDK version 1.5.9 onwards). Priority >FTTraceConfig.traceInterceptor
.FTURLSessionDelegate
supports custom additional properties for RUM Resource via theprovider
block. Priority >FTRumConfig.resourcePropertyProvider
.FTURLSessionDelegate
supports using theerrorFilter
block to customize interception of SessionTask Error (supported from SDK version 1.5.17 onwards).- return YES: Intercepts, no
network_error
added to RUM-Error - return NO: Does not intercept, adds
network_error
to RUM-Error - When used with
FTRumConfig.enableTraceUserResource
andFTTraceConfig.enableAutoTrace
, priority: Custom > Auto Collection.
Below are three methods to meet different user scenarios.
Method One¶
Directly set the delegate object of URLSession to an instance of FTURLSessionDelegate
.
id<NSURLSessionDelegate> delegate = [[FTURLSessionDelegate alloc]init];
// Add custom RUM resource attributes, it is recommended to add project abbreviation prefixes to tag names, e.g., `df_tag_name`.
delegate.provider = ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
return @{@"df_requestbody":body};
};
// Support custom trace, return TraceContext if intercepted, return nil if not intercepted
delegate.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
FTTraceContext *context = [FTTraceContext new];
context.traceHeader = @{@"trace_key":@"trace_value"};
context.traceId = @"trace_id";
context.spanId = @"span_id";
return context;
};
// Customize whether to intercept SessionTask Error. return YES: intercept, no `network_error` added to RUM-Error
delegate.errorFilter = ^BOOL(NSError * _Nonnull error) {
return error.code == NSURLErrorCancelled;
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:delegate delegateQueue:nil];
let delegate = FTURLSessionDelegate.init()
// Add custom RUM resource attributes, it is recommended to add project abbreviation prefixes to tag names, e.g., `df_tag_name`.
delegate.provider = { request,response,data,error in
var extraData:Dictionary<String, Any> = Dictionary()
if let data = data,let requestBody = String(data: data, encoding: .utf8) {
extraData["df_requestBody"] = requestBody
}
if let error = error {
extraData["df_error"] = error.localizedDescription
}
return extraData
}
// Support custom trace, return TraceContext if intercepted, return nil if not intercepted
delegate.traceInterceptor = { request in
let traceContext = FTTraceContext()
traceContext.traceHeader = ["trace_key":"trace_value"]
traceContext.spanId = "spanId"
traceContext.traceId = "traceId"
return traceContext
}
delegate.errorFilter = { error in
return (error as? URLError)?.code == .cancelled
}
let session = URLSession.init(configuration: URLSessionConfiguration.default, delegate:delegate
, delegateQueue: nil)
Method Two¶
Make the delegate object of URLSession inherit from the FTURLSessionDelegate
class.
If the delegate object implements the following methods, ensure to call the corresponding methods in the parent class.
-URLSession:dataTask:didReceiveData:
-URLSession:task:didCompleteWithError:
-URLSession:task:didFinishCollectingMetrics:
@interface InstrumentationInheritClass:FTURLSessionDelegate
@property (nonatomic, strong) NSURLSession *session;
@end
@implementation InstrumentationInheritClass
-(instancetype)init{
self = [super init];
if(self){
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
// Add custom RUM resource attributes, it is recommended to add project abbreviation prefixes to tag names, e.g., `df_tag_name`.
self.provider = ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
return @{@"df_requestbody":body};
};
// Support custom trace, return TraceContext if intercepted, return nil if not intercepted
self.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
FTTraceContext *context = [FTTraceContext new];
context.traceHeader = @{@"trace_key":@"trace_value"};
context.traceId = @"trace_id";
context.spanId = @"span_id";
return context;
};
// Customize whether to intercept SessionTask Error. return YES: intercept, no `network_error` added to RUM-Error
self.errorFilter = ^BOOL(NSError * _Nonnull error) {
return error.code == NSURLErrorCancelled;
};
}
return self;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
// Must call parent class method
[super URLSession:session task:task didFinishCollectingMetrics:metrics];
// Your own logic
// ......
}
@end
class InheritHttpEngine:FTURLSessionDelegate {
var session:URLSession?
override init(){
session = nil
super.init()
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
session = URLSession.init(configuration: configuration, delegate:self, delegateQueue: nil)
override init() {
super.init()
// Add custom RUM resource attributes, it is recommended to add project abbreviation prefixes to tag names, e.g., `df_tag_name`.
provider = { request,response,data,error in
var extraData:Dictionary<String, Any> = Dictionary()
if let data = data,let requestBody = String(data: data, encoding: .utf8) {
extraData["df_requestBody"] = requestBody
}
if let error = error {
extraData["df_error"] = error.localizedDescription
}
return extraData
}
// Support custom trace, return TraceContext if intercepted, return nil if not intercepted
traceInterceptor = { request in
let traceContext = FTTraceContext()
traceContext.traceHeader = ["trace_key":"trace_value"]
traceContext.spanId = "spanId"
traceContext.traceId = "traceId"
return traceContext
}
errorFilter = { error in
return (error as? URLError)?.code == .cancelled
}
}
}
override func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
// Must call parent class method
super.urlSession(session, task: task, didFinishCollecting: metrics)
// User's own logic
// ......
}
}
Method Three¶
Make the delegate object of URLSession conform to the FTURLSessionDelegateProviding
protocol.
- Implement the get method of the
ftURLSessionDelegate
property in the protocol - Forward the following URLSession delegate methods to
ftURLSessionDelegate
for SDK data collection.-URLSession:dataTask:didReceiveData:
-URLSession:task:didCompleteWithError:
-URLSession:task:didFinishCollectingMetrics:
@interface UserURLSessionDelegateClass:NSObject<NSURLSessionDataDelegate,FTURLSessionDelegateProviding>
@end
@implementation UserURLSessionDelegateClass
@synthesize ftURLSessionDelegate = _ftURLSessionDelegate;
- (nonnull FTURLSessionDelegate *)ftURLSessionDelegate {
if(!_ftURLSessionDelegate){
_ftURLSessionDelegate = [[FTURLSessionDelegate alloc]init];
// Add custom RUM resource attributes, it is recommended to add project abbreviation prefixes to tag names, e.g., `df_tag_name`.
_ftURLSessionDelegate.provider = ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
return @{@"df_requestbody":body};
};
// Support custom trace, return TraceContext if intercepted, return nil if not intercepted
_ftURLSessionDelegate.requestInterceptor = ^NSURLRequest * _Nonnull(NSURLRequest * _Nonnull request) {
NSDictionary *traceHeader = [[FTExternalDataManager sharedManager] getTraceHeaderWithUrl:request.URL];
NSMutableURLRequest *newRequest = [request mutableCopy];
if(traceHeader){
for (NSString *key in traceHeader.allKeys) {
[newRequest setValue:traceHeader[key] forHTTPHeaderField:key];
}
}
return newRequest;
};
// Customize whether to intercept SessionTask Error. return YES: intercept, no `network_error` added to RUM-Error
_ftURLSessionDelegate.errorFilter = ^BOOL(NSError * _Nonnull error) {
return error.code == NSURLErrorCancelled;
};
}
return _ftURLSessionDelegate;
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
[self.ftURLSessionDelegate URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
[self.ftURLSessionDelegate URLSession:session task:task didCompleteWithError:error];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
[self.ftURLSessionDelegate URLSession:session task:task didFinishCollectingMetrics:metrics];
}
@end
class HttpEngine:NSObject,URLSessionDataDelegate,FTURLSessionDelegateProviding {
var ftURLSessionDelegate: FTURLSessionDelegate = FTURLSessionDelegate()
var session:URLSession?
override init(){
session = nil
super.init()
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
session = URLSession.init(configuration: configuration, delegate:self, delegateQueue: nil)
// Add custom RUM resource attributes, it is recommended to add project abbreviation prefixes to tag names, e.g., `df_tag_name`.
ftURLSessionDelegate.provider = { request,response,data,error in
var extraData:Dictionary<String, Any> = Dictionary()
if let data = data,let requestBody = String(data: data, encoding: .utf8) {
extraData["df_requestBody"] = requestBody
}
if let error = error {
extraData["df_error"] = error.localizedDescription
}
return extraData
}
// Support custom trace, return TraceContext if intercepted, return nil if not intercepted
ftURLSessionDelegate.traceInterceptor = { request in
let traceContext = FTTraceContext()
traceContext.traceHeader = ["trace_key":"trace_value"]
traceContext.spanId = "spanId"
traceContext.traceId = "traceId"
return traceContext
}
ftURLSessionDelegate.errorFilter = { error in
return (error as? URLError)?.code == .cancelled
}
}
// The following method must be implemented
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
ftURLSessionDelegate.urlSession(session, dataTask: dataTask, didReceive: data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
ftURLSessionDelegate.urlSession(session, task: task, didFinishCollecting: metrics)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
ftURLSessionDelegate.urlSession(session, task: task, didCompleteWithError: error)
}
}
Binding and Unbinding Users¶
Use FTMobileAgent
to bind user information and unbind the current user.
/// Bind user information, call this method after successful user login to bind user information
///
/// - Parameters:
/// - Id: User ID
/// - userName: User name (optional)
/// - userEmail: User email (optional)
/// - extra: Additional user information (optional)
- (void)bindUserWithUserID:(NSString *)Id userName:(nullable NSString *)userName userEmail:(nullable NSString *)userEmail extra:(nullable NSDictionary *)extra;
/// Unbind the current user, call this method after user logout to unbind user information
- (void)unbindUser;
/// Bind user information, call this method after successful user login to bind user information
///
/// - Parameters:
/// - Id: User ID
/// - userName: User name (optional)
/// - userEmail: User email (optional)
/// - extra: Additional user information (optional)
open func bindUser(withUserID Id: String, userName: String?, userEmail: String?, extra: [AnyHashable : Any]?)
/// Unbind the current user, call this method after user logout to unbind user information
open func unbindUser()
Please refer to here for instructions on adding extra
.
Closing the SDK¶
Use FTMobileAgent
to close the SDK. If dynamically changing the SDK configuration, it should be closed first to avoid generating incorrect data.
Clearing SDK Cache Data¶
Use FTMobileAgent
to clear unsent cached data.
Manually Syncing Data¶
Use FTMobileAgent
to manually sync data.
Needed only when
FTMobileConfig.autoSync = NO
, otherwise automatic syncing is used
Manually Syncing Dynamic Configuration¶
Use FTMobileAgent
to manually sync dynamic configurations.
Calling this method only takes effect when
FTMobileConfig.remoteConfiguration = YES
/// Manually update remote configuration, frequency of calls is affected by `FTMobileConfig.remoteConfigMiniUpdateInterval`
+ (void)updateRemoteConfig;
/// Manually update remote configuration, this method ignores `FTMobileConfig.remoteConfigMiniUpdateInterval` settings
/// - Parameters:
/// - miniUpdateInterval: Remote configuration time interval, unit seconds [0,)
/// - callback: Returns the update result
+ (void)updateRemoteConfigWithMiniUpdateInterval:(int)miniUpdateInterval callback:(void (^)(BOOL success, NSDictionary<NSString *, id> * _Nullable config))callback;
/// Manually update remote configuration, frequency of calls is affected by `FTMobileConfig.remoteConfigMiniUpdateInterval`
open class func updateRemoteConfig()
/// Manually update remote configuration, this method ignores `FTMobileConfig.remoteConfigMiniUpdateInterval` settings
/// - Parameters:
/// - miniUpdateInterval: Remote configuration time interval, unit seconds [0,)
/// - callback: Returns the update result
open class func updateRemoteConfig(withMiniUpdateInterval miniUpdateInterval: Int32, callback: @escaping (Bool, [String : Any]?) -> Void)
Adding Custom Tags¶
Use FTMobileAgent
to dynamically add tags while the SDK is running. Please refer to here for instructions on adding rules.
/// Add SDK-wide tag, applicable to RUM and Log data
/// - Parameter context: Custom data
+ (void)appendGlobalContext:(NSDictionary <NSString*,id>*)context;
/// Add custom tag for RUM, applicable to RUM data
/// - Parameter context: Custom data
+ (void)appendRUMGlobalContext:(NSDictionary <NSString*,id>*)context;
/// Add global tag for Log, applicable to Log data
/// - Parameter context: Custom data
+ (void)appendLogGlobalContext:(NSDictionary <NSString*,id>*)context;
/// Add SDK-wide tag, applicable to RUM and Log data
/// - Parameter context: Custom data
open class func appendGlobalContext(_ context: [String : Any])
/// Add custom tag for RUM, applicable to RUM data
/// - Parameter context: Custom data
open class func appendRUMGlobalContext(_ context: [String : Any])
/// Add global tag for Log, applicable to Log data
/// - Parameter context: Custom data
open class func appendLogGlobalContext(_ context: [String : Any])
Uploading Symbol Files¶
Adding Run Script in Xcode (Only supports datakit [local deployment])¶
-
In XCode, add a custom Run Script Phase:
Build Phases -> + -> New Run Script Phase
-
Copy the script into the build phase run script of the Xcode project, setting parameters such as <app_id>, <datakit_address>, <env>, <dataway_token> in the script.
-
Script: FTdSYMUploader.sh
# Parameters to configure in the script
#<app_id>
FT_APP_ID="YOUR_APP_ID"
#<datakit_address>
FT_DATAKIT_ADDRESS="YOUR_DATAKIT_ADDRESS"
#<env> Environment field. Attribute value: prod/gray/pre/common/local. Must be consistent with SDK settings
FT_ENV="common"
#<dataway_token> Token in dataway configuration file datakit.conf
FT_TOKEN="YOUR_DATAWAY_TOKEN"
# Whether to only zip dSYM files (optional, default 0 uploads), 1=no upload, only pack dSYM zip, 0=upload, DSYM_SYMBOL.zip file path can be viewed in script output logs by searching FT_DSYM_ZIP_FILE
FT_DSYM_ZIP_ONLY=0
If you need to upload symbol files for multiple environments, please refer to the following approach.
Multi-environment Configuration Parameters¶
Example: Use .xcconfig file to configure multiple environments
1. Create an xcconfig configuration file and define variables in the .xcconfig file
Refer to Adding Build Configuration File to Your Project for creating an xcconfig configuration file.
// If Cocoapods are used, the path to pods'.xcconfig needs to be added to your .xcconfig file
// Import pods'.xcconfig
#include "Pods/Target Support Files/Pods-GuanceDemo/Pods-GuanceDemo.pre.xcconfig"
SDK_APP_ID = app_id_common
SDK_ENV = common
// URL // requires $()
SDK_DATAKIT_ADDRESS = http:.$()/xxxxxxxx:9529
SDK_DATAWAY_TOKEN = token
At this point, the user-defined parameters have been automatically added. You can check them by navigating to Target —> Build Settings -> + -> Add User-Defined Setting
2. Configure script parameters
# Parameters to configure in the script
#<app_id>
FT_APP_ID=${SDK_APP_ID}
#<datakit_address>
FT_DATAKIT_ADDRESS=${SDK_DATAKIT_ADDRESS}
#<dev> Environment field. Attribute value: prod/gray/pre/common/local. Must be consistent with SDK settings
FT_ENV=${SDK_ENV}
#<dataway_token> Token in datakit.conf
FT_TOKEN=${SDK_DATAWAY_TOKEN}
3. Configure SDK
Add parameter mapping in the Info.plist
file
Get parameters from the Info.plist
file and configure the SDK accordingly
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let info = Bundle.main.infoDictionary!
let appid:String = info["SDK_APP_ID"] as! String
let env:String = info["SDK_ENV"] as! String
let config = FTMobileConfig.init(datakitUrl: UserDefaults.datakitURL)
config.enableSDKDebugLog = true
config.autoSync = false
config.env = env
.....
}
For detailed steps, please refer to the multi-environment usage in the SDK Demo.
Running Script in Terminal¶
Upload Symbol File (Only supports datakit [local deployment])¶
sh FTdSYMUploader.sh <datakit_address> <app_id> <version> <env> <dataway_token> <dSYMBOL_src_dir>
Example:
sh FTdSYMUploader.sh http://10.0.0.1:9529 appid_mock 1.0.6 prod tkn_mock /Users/mock/Desktop/dSYMs
Compress Symbol Files Only¶
sh FTdSYMUploader.sh -dSYMFolderPath <dSYMBOL_src_dir> -z
Example:
sh FTdSYMUploader.sh -dSYMFolderPath /Users/mock/Desktop/dSYMs -z DSYM Zip file path can be viewed in script output logs by searching
FT_DSYM_ZIP_FILE
.
Parameter Description:
<datakit_address>
: Address of the DataKit service, e.g.,http://localhost:9529
<app_id>
: Corresponding RUMapplicationId
<env>
: Corresponding RUMenv
<version>
: Application'sversion
, value ofCFBundleShortVersionString
<dataway_token>
:dataway
token in the configuration filedatakit.conf
<dSYMBOL_src_dir>
: Directory path containing all.dSYM
files.
Manual Upload¶
Widget Extension Data Collection¶
Widget Extension Data Collection Support¶
- Custom logging with Logger
- Distributed tracing with Trace
- RUM data collection
- Manual collection (RUM User Data Tracking)
- Automatic collection of crash logs and HTTP Resource data
Since HTTP Resource data is bound to the View, users need to manually collect View data.
Widget Extension Collection Configuration¶
Use FTExtensionConfig
to configure automatic switches for Widget Extension data collection and file sharing Group Identifier. Other configurations use the already set configurations from the main project SDK.
Field | Type | Mandatory | Description |
---|---|---|---|
groupIdentifier | NSString | Yes | File sharing Group Identifier |
enableSDKDebugLog | BOOL | No (default NO) | Determine if SDK should print debug logs |
enableTrackAppCrash | BOOL | No (default NO) | Determine if crash logs should be collected |
enableRUMAutoTraceResource | BOOL | No (default NO) | Determine if user network requests should be traced (only for native HTTP) |
enableTracerAutoTrace | BOOL | No (default NO) | Determine if automatic HTTP distributed tracing should be enabled |
memoryMaxCount | NSInteger | No (default 1000 entries) | Maximum number of data entries saved in Widget Extension |
Example of using Widget Extension SDK:
let extensionConfig = FTExtensionConfig.init(groupIdentifier: "group.identifier")
extensionConfig.enableTrackAppCrash = true
extensionConfig.enableRUMAutoTraceResource = true
extensionConfig.enableTracerAutoTrace = true
extensionConfig.enableSDKDebugLog = true
FTExtensionManager.start(with: extensionConfig)
FTExternalDataManager.shared().startView(withName: "WidgetDemoEntryView")
Also, in the main project, when setting up FTMobileConfig
, groupIdentifiers
must be set.
```swift let config = FTMobileConfig.init(metricsUrl: url) config.enableSDKDebugLog= true config.groupIdentifiers = ["group.com.ft.widget.demo"]
```
Uploading Data Collected by Widget Extension¶
The Widget Extension SDK only implements data collection; the logic for uploading data is handled by the main project's SDK. The timing of data synchronization to the main project is determined by the user.
Usage¶
// Called in the main project
/// Track cached data from App Extension groupIdentifier
/// - Parameters:
/// - groupIdentifier: groupIdentifier
/// - completion: Callback after tracking completes
- (void)trackEventFromExtensionWithGroupIdentifier:(NSString *)groupIdentifier completion:(nullable void (^)(NSString *groupIdentifier, NSArray *events)) completion;
Code Example¶
WebView Data Monitoring¶
WebView data monitoring requires integrating the Web Monitoring SDK on the page accessed by the WebView.
Data Masking¶
If you wish to fully mask fields, it is recommended to use FTMobileConfig.dataModifier
, which offers better performance. For detailed rule-based replacements, use FTMobileConfig.lineDataModifier
.
Avoid using complex or high-latency methods in callback functions, as this can significantly affect SDK write performance.
FTMobileConfig *config = [[FTMobileConfig alloc]initWithDatakitUrl:DatakitUrl];
config.dataModifier = ^id _Nullable(NSString * _Nonnull key, id _Nonnull value) {
if ([key isEqualToString: @"device_uuid"]) {
return @"xxx";
}
return nil;
};
config.lineDataModifier = ^NSDictionary<NSString *,id> * _Nullable(NSString * _Nonnull measurement, NSDictionary<NSString *,id> * _Nonnull data) {
if ([measurement isEqualToString:@"view"]) {
return @{@"view_url":@"xxx"};
}
return nil;
};
let config = FTMobileConfig(datakitUrl: DatakitUrl)
config.dataModifier = { (key: String, value: Any) -> Any? in
if key == "device_uuid" {
return "xxx"
}
return nil
}
config.lineDataModifier = { (measurement: String, data: [String: Any]) -> [String: Any]? in
if measurement == "view" {
return ["view_url": "xxx"]
}
return nil
}
Example of Using Custom Tags¶
Configuration via Compilation Settings¶
Multiple Configurations can be created and values set using precompilation directives.
- Create multiple Configurations
- Set preset properties to distinguish between different Configurations
- Use precompilation directives
//Target -> Build Settings -> GCC_PREPROCESSOR_DEFINITIONS for configuration of preset definitions
#if PRE
#define Track_id @"0000000001"
#define STATIC_TAG @"preprod"
#elif DEVELOP
#define Track_id @"0000000002"
#define STATIC_TAG @"common"
#else
#define Track_id @"0000000003"
#define STATIC_TAG @"prod"
#endif
FTRumConfig *rumConfig = [[FTRumConfig alloc]init];
rumConfig.globalContext = @{@"track_id":Track_id,@"static_tag":STATIC_TAG};
... //Other settings operations
[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];
You can also refer to the Multi-environment Configuration Parameters method for configuration.
Runtime File Reading/Writing Method¶
Since globalContext settings made after RUM starts will not take effect, users can save locally and reapply during the next app startup.
- Save files locally through
NSUserDefaults
and configure the SDK usage. When configuring the SDK, add code to retrieve tag data.
NSString *dynamicTag = [[NSUserDefaults standardUserDefaults] valueForKey:@"DYNAMIC_TAG"]?:@"NO_VALUE";
FTRumConfig *rumConfig = [[FTRumConfig alloc]init];
rumConfig.globalContext = @{@"dynamic_tag":dynamicTag};
... //Other settings operations
[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];
- Add a method to change file data anywhere:
- Restart the app to apply changes.
Adding Tags During SDK Runtime¶
After the SDK has been initialized, use [FTMobileAgent appendGlobalContext:globalContext]
, [FTMobileAgent appendRUMGlobalContext:globalContext]
, and [FTMobileAgent appendLogGlobalContext:globalContext]
to dynamically add tags. Once set, these tags take immediate effect, and subsequent RUM or Log reports will automatically include the tag data. This method is suitable for scenarios where data needs to be obtained later, such as when tag data requires network requests to fetch.
//SDK initialization pseudo-code
[FTMobileAgent startWithConfigOptions:config];
-(void)getInfoFromNet:(Info *)info{
NSDictionary *globalContext = @{@"delay_key", info.value}
[FTMobileAgent appendGlobalContext:globalContext];
}
tvOS Data Collection¶
API >= tvOS 12.0
The initialization and usage of the SDK are consistent with iOS.
Note that tvOS does NOT support:
-
WebView
data monitoring -
Device battery monitoring under
FTRumConfig.errorMonitorType
Frequently Asked Questions¶
About Crash Log Analysis¶
In both Debug and Release modes during development, thread backtraces captured during crashes are symbolized. However, distribution packages without symbol tables will show image names instead of valid code symbols for exception threads. The crash log information obtained will contain hexadecimal memory addresses instead of code references, so these addresses need to be resolved to corresponding classes and methods.
How to Find dSYM Files After Compilation or Packaging¶
- In Xcode, dSYM files are usually generated alongside the compiled .app file and located in the same directory.
- If archiving the project, select
Window
menu in Xcode, chooseOrganizer
, then select the corresponding archive file. Right-click the archive file and choose “Show in Finder.” Locate the.xcarchive
file in Finder, right-click it, and chooseShow Package Contents
. Then navigate to thedSYMs
folder to find the corresponding dSYM file.
Why Can't I Find the dSYM File After Compiling with XCode?¶
By default, XCode generates dSYM files when compiling in Release mode, but not in Debug mode. Check the following Xcode configurations:
Build Settings -> Code Generation -> Generate Debug Symbols -> Yes
Build Settings -> Build Option -> Debug Information Format -> DWARF with dSYM File
What to Do If BitCode Is Enabled?¶
When uploading your bitcode-enabled App to the App Store, check the box to declare generation of symbol files (dSYM files) during submission:
- Before configuring symbol files, download the dSYM files for the corresponding version from App Store Connect to your local machine, then process and upload the symbol files according to input parameters.
- There’s no need to integrate the script into the Target of your Xcode project, nor to generate symbol files from locally compiled dSYM files, because the symbol information in locally compiled dSYM files is hidden. If uploaded, the results will appear like “__hiden#XXX”.
How to Retrieve dSYM Files for Published Apps on the App Store?¶
Distribution Options When Uploading to App Store Connect | dSym Files |
---|---|
Don’t include bitcode Upload symbols |
Recovered via Xcode |
Include bitcode Upload symbols |
Recovered via iTunes Connect Recover via Xcode, but need to de-obfuscate using .bcsymbolmap . |
Include bitcode Don’t upload symbols |
Recovered via Xcode, need to de-obfuscate using .bcsymbolmap . |
Don’t include bitcode Don’t upload symbols |
Recovered via Xcode |
Recovering via Xcode¶
-
Xcode -> Window -> Organizer
-
Select the
Archives
tab -
Find the published archive package, right-click the corresponding archive package, and select
Show in Finder
-
Right-click the located archive file and select
Show Package Contents
-
Navigate to the
dSYMs
directory, which contains the downloaded dSYM files
Recovering via iTunes Connect¶
- Log in to App Store Connect;
- Go to "My Apps (My Apps)"
- Select a version under "App Store" or "TestFlight," click "Build Metadata," and on this page, click the "Download dSYM" button to download the dSYM files
Deobfuscating with .bcsymbolmap¶
When recovering dSYM files through Xcode, the BCSymbolMaps directory can be seen
Open the terminal and use the following command for deobfuscation
xcrun dsymutil -symbol-map <BCSymbolMaps_path> <.dSYM_path>
Adding Global Variables to Avoid Conflicting Fields¶
To avoid conflicts between custom fields and SDK data, it is recommended to prefix tag names with the project abbreviation, e.g., df_tag_name
. You can query source code for keys used in the project. When variables defined globally in the SDK conflict with those in RUM or Log, RUM or Log will overwrite the global variables in the SDK.