Skip to content

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.

Application Integration

  1. Navigate to User Access Monitoring > Create Application > iOS;
  2. Enter an application name;
  3. Enter an application ID;
  4. 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

tvOS

Source Code Repository: https://github.com/GuanceCloud/datakit-ios

Demo: https://github.com/GuanceDemo/guance-app-demo

  1. Configure the Podfile file.

    • Using Dynamic Library

      use_frameworks!
      def shared_pods
        pod 'FTMobileSDK', '[latest_version]'
        # If you need to collect widget Extension data
        pod 'FTMobileSDK', :subspecs => ['Extension'] 
      end
      
      # Main project
      target 'yourProjectName' do
        shared_pods
      end
      
      # Widget Extension
      target 'yourWidgetExtensionName' do
        shared_pods
      end
      

    • Using Static Library

      use_modular_headers!
      # Main project
      target 'yourProjectName' do
        pod 'FTMobileSDK', '[latest_version]'
      end
      
      # Widget Extension
      target 'yourWidgetExtensionName' do
        pod 'FTMobileSDK', :subspecs => ['Extension'] 
      end
      

    • Download the repository locally for use

    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 containing FTMobileSDK.podspec.

    FTMobileSDK.podspec file:

    Modify the s.version and s.source fields in the FTMobileSDK.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 the SDK_VERSION in FTMobileSDK/FTMobileAgent/Core/FTMobileAgentVersion.h.

    s.source: tag => s.version

  2. Execute pod install in the directory containing the Podfile to install the SDK.

  1. Configure the Cartfile file.

    github "GuanceCloud/datakit-ios" == [latest_version]
    

  2. 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:

      carthage update --platform iOS --use-xcframeworks
      

    • For tvOS platform:

      carthage update --platform tvOS --use-xcframeworks
      

    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.

  3. In TARGETS -> Build Setting -> Other Linker Flags, add -ObjC.

  4. When integrating via Carthage, supported SDK versions are:

    FTMobileAgent: >=1.3.4-beta.2

    FTMobileExtension: >=1.4.0-beta.1

  1. Select PROJECT -> Package Dependency, then click the + under the Packages section.

  2. In the search field on the pop-up page, enter https://github.com/GuanceCloud/datakit-ios.git.

  3. After Xcode successfully fetches the package, it will display the SDK configuration page.

    Dependency Rule : It is recommended to select Up 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.

  4. 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 the Add Package button. At this point, the SDK has been successfully added.

    FTMobileSDK: Added to the main project Target

    FTMobileExtension: Added to the Widget Extension Target

    If your project is managed by SPM, add the SDK as a dependency by adding the dependencies to Package.swift.

    // Main project
    dependencies: [
        .package(name: "FTMobileSDK", url: "https://github.com/GuanceCloud/datakit-ios.git",
        .upToNextMajor(from: "[latest_version]"))]
    
  5. Swift Package Manager is supported starting from version 1.4.0-beta.1.

Adding Header Files

// CocoaPods, SPM 
#import "FTMobileSDK.h"
// Carthage 
#import <FTMobileSDK/FTMobileSDK.h>
import FTMobileSDK

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

   // Enable trace
   FTTraceConfig *traceConfig = [[FTTraceConfig alloc]init];
   traceConfig.enableLinkRumData = YES;
     traceConfig.enableAutoTrace = YES;
   traceConfig.networkTraceType = FTNetworkTraceTypeDDtrace;
   [[FTMobileAgent sharedInstance] startTraceWithConfigOptions:traceConfig];
   let traceConfig = FTTraceConfig.init()
   traceConfig.enableLinkRumData = true
   traceConfig.enableAutoTrace = true
   FTMobileAgent.sharedInstance().startTrace(withConfigOptions: traceConfig)
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

// startAction
[[FTExternalDataManager sharedManager] startAction:@"action" actionType:@"click" property:@{@"action_property":@"testActionProperty1"}];
// addAction
[[FTExternalDataManager sharedManager] addAction:@"action" actionType:@"click" property:@{@"action_property":@"testActionProperty1"}];
// startAction
FTExternalDataManager.shared().startAction("custom_action", actionType: "click",property: nil)
// addAction
FTExternalDataManager.shared().addAction("custom_action", actionType: "click",property: nil)

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

/// Add Stalling Event
/// - Parameters:
///   - stack: Stall stack
///   - duration: Stall duration (nanoseconds)
///   - property: Event custom attribute (optional)
- (void)addLongTaskWithStack:(NSString *)stack duration:(NSNumber *)duration property:(nullable NSDictionary *)property;
/// Add Stalling Event
/// - Parameters:
///   - stack: Stall stack
///   - duration: Stall duration (nanoseconds)
///   - property: Event custom attribute (optional)
func addLongTask(withStack: String, duration: NSNumber, property: [AnyHashable : Any]?)

Code Example

// Scenario 1
[[FTExternalDataManager sharedManager] addLongTaskWithStack:@"stack string" duration:@1000000000];
// Scenario 2: Dynamic parameters
[[FTExternalDataManager sharedManager] addLongTaskWithStack:@"stack string" duration:@1000000000 property:@{@"custom_key":@"custom_value"}];
// Scenario 1
FTExternalDataManager.shared().addLongTask(withStack: "stack string", duration: 1000000000)
// Scenario 2: Dynamic parameters
FTExternalDataManager.shared().addLongTask(withStack: "stack string", duration: 1000000000 ,property: [["custom_key":"custom_value"]])

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

/// Event severity and status, default: FTStatusInfo
typedef NS_ENUM(NSInteger, FTLogStatus) {
    /// Informational
    FTStatusInfo         = 0,
    /// Warning
    FTStatusWarning,
    /// Error
    FTStatusError,
    /// Critical
    FTStatusCritical,
    /// Recovery
    FTStatusOk,
};
/// Event severity and status, default: FTStatusInfo
public enum FTLogStatus : Int, @unchecked Sendable {
    /// Informational
    case statusInfo = 0
    /// Warning
    case statusWarning = 1
    /// Error
    case statusError = 2
    /// Critical
    case statusCritical = 3
    /// Recovery
    case statusOk = 4
}

Code Example

// If the SDK fails to initialize successfully, adding custom logs will also fail
[[FTLogger sharedInstance] info:@"test" property:@{@"custom_key":@"custom_value"}];
// If the SDK fails to initialize successfully, adding custom logs will also fail
FTLogger.shared().info("contentStr", property: ["custom_key":"custom_value"])

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] [IOS APP] [INFO] content ,{K=V,...,Kn=Vn}

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 intercepting URLResquest through the traceInterceptor 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 the provider block. Priority > FTRumConfig.resourcePropertyProvider.
  • FTURLSessionDelegate supports using the errorFilter 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 and FTTraceConfig.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.

+ (void)shutDown;
open class func shutDown()

Clearing SDK Cache Data

Use FTMobileAgent to clear unsent cached data.

+ (void)clearAllData;
open class func clearAllData()

Manually Syncing Data

Use FTMobileAgent to manually sync data.

Needed only when FTMobileConfig.autoSync = NO, otherwise automatic syncing is used

- (void)flushSyncData;
func flushSyncData()

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

  1. In XCode, add a custom Run Script Phase: Build Phases -> + -> New Run Script Phase

  2. 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.

  3. 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

Script: FTdSYMUploader.sh

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 RUM applicationId
  • <env>: Corresponding RUM env
  • <version>: Application's version, value of CFBundleShortVersionString
  • <dataway_token>: dataway token in the configuration file datakit.conf
  • <dSYMBOL_src_dir>: Directory path containing all .dSYM files.

Manual Upload

Upload Sourcemap

Widget Extension Data Collection

Widget Extension Data Collection Support

  • Custom logging with Logger
  • Distributed tracing with Trace
  • RUM data collection

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.

// Main project
 FTMobileConfig *config = [[FTMobileConfig alloc]initWithMetricsUrl:url];
 config.enableSDKDebugLog = YES;
 config.groupIdentifiers = @[@"group.com.ft.widget.demo"]; 

```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;
/// Track cached data from App Extension groupIdentifier
/// - Parameters:
///   - groupIdentifier: groupIdentifier
///   - completion: Callback after tracking completes
open func trackEventFromExtension(withGroupIdentifier groupIdentifier: String, completion: ((String, [Any]) -> Void)? = nil)

Code Example

// In the main project
-(void)applicationDidBecomeActive:(UIApplication *)application{
    [[FTMobileAgent sharedInstance] trackEventFromExtensionWithGroupIdentifier:@"group.identifier" completion:nil];
}
func applicationDidBecomeActive(_ application: UIApplication) {
   FTMobileAgent.sharedInstance().trackEventFromExtension(withGroupIdentifier: "group.identifier" )     
}

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.

  1. Create multiple Configurations

  1. Set preset properties to distinguish between different Configurations

  1. 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.

  1. 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];
  1. Add a method to change file data anywhere:
 [[NSUserDefaults standardUserDefaults] setValue:@"dynamic_tags" forKey:@"DYNAMIC_TAG"];
  1. 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, choose Organizer, 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 choose Show Package Contents. Then navigate to the dSYMs 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
  1. Xcode -> Window -> Organizer

  2. Select the Archives tab

  3. Find the published archive package, right-click the corresponding archive package, and select Show in Finder

  4. Right-click the located archive file and select Show Package Contents

  5. Navigate to the dSYMs directory, which contains the downloaded dSYM files

Recovering via iTunes Connect
  1. Log in to App Store Connect;
  2. Go to "My Apps (My Apps)"
  3. 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.

Feedback

Is this page helpful? ×