RUM iOS Advanced Configuration

RUM iOS Advanced Configuration

If you have not set up the SDK yet, follow the in-app setup instructions or refer to the iOS RUM setup documentation.

Enrich user sessions

iOS RUM automatically tracks attributes such as user activity, screens, errors, and network requests. See the RUM Data Collection documentation to learn about the RUM events and default attributes. You can further enrich user session information and gain finer control over the attributes collected by tracking custom events.

Custom Views

In addition to tracking views automatically, you can also track specific distinct views (viewControllers) when they become visible and interactive. Stop tracking when the view is no longer visible using the following methods in Global.rum:

  • .startView(viewController:)
  • .stopView(viewController:)

Example:

// in your `UIViewController`:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    Global.rum.startView(viewController: self)
}

override func viewDidDisappear(_ animated: Bool) {
  super.viewDidDisappear(animated)
  Global.rum.stopView(viewController: self)
}

Find more details and available options in DDRUMMonitor class.

Add your own performance timing

In addition to RUM’s default attributes, you can measure where your application is spending its time by using the addTiming(name:) API. The timing measure is relative to the start of the current RUM view. For example, you can time how long it takes for your hero image to appear:

func onHeroImageLoaded() {
    Global.rum.addTiming(name: "hero_image")
} 

Once the timing is sent, the timing will be accessible as @view.custom_timings.<timing_name> (For example, @view.custom_timings.hero_image). You must create a measure before graphing it in RUM analytics or in dashboards.

Custom Actions

In addition to tracking actions automatically, you can also track specific custom user actions (taps, clicks, scrolls, etc.) with addUserAction(type:name:) API. To manually register instantaneous RUM actions (e.g: .tap), on Global.rum use:

  • .addUserAction(type:name:)

or for continuous RUM actions (e.g: .scroll), use:

  • .startUserAction(type:name:)
  • .stopUserAction(type:)

Example:

// in your `UIViewController`:

@IBAction func didTapDownloadResourceButton(_ sender: UIButton) {
    Global.rum.addUserAction(
        type: .tap,
        name: sender.currentTitle ?? "",
    )
}

Note: When using .startUserAction(type:name:) and .stopUserAction(type:), the action type must be the same. This is necessary for the SDK to match an action start with its completion.

Find more details and available options in DDRUMMonitor class.

Custom Resources

In addition to tracking resources automatically, you can also track specific custom resources, such as network requests or third party provider APIs. Use the following methods on Global.rum to manually collect RUM resources:

  • .startResourceLoading(resourceKey:request:)
  • .stopResourceLoading(resourceKey:response:)
  • .stopResourceLoadingWithError(resourceKey:error:)
  • .stopResourceLoadingWithError(resourceKey:errorMessage:)

Example:

// in your network client:

Global.rum.startResourceLoading(
    resourceKey: "resource-key", 
    request: request
)

Global.rum.stopResourceLoading(
    resourceKey: "resource-key",
    response: response
)

Note: The String used for resourceKey in both calls must be unique for the resource you are calling. This is necessary for the SDK to match a resource’s start with its completion.

Find more details and available options in DDRUMMonitor class.

Custom Errors

To track specific errors, notify Global.rum when an error occurs with the message, source, exception, and additional attributes. Refer to the Error Attributes documentation.

Global.rum.addError(message: "error message.")

For more details and available options, refer to the code documentation comments in DDRUMMonitor class.

Track custom global attributes

In addition to the default RUM attributes captured by the mobile SDK automatically, you can choose to add additional contextual information, such as custom attributes, to your RUM events to enrich your observability within Datadog.

Custom attributes allow you to filter and group information about observed user behavior (such as cart value, merchant tier, or ad campaign) with code-level information (such as backend services, session timeline, error logs, and network health).

Set a custom global attribute

To set a custom global attribute, use Global.rum.addAttribute(forKey:value:).

  • To add an attribute, use Global.rum.setAttribute(forKey: "some key", value: "some value").
  • To update the value, use Global.rum.setAttribute(forKey: "some key", value: "some other value").
  • To remove the key, use Global.rum.removeAttribute(forKey: "some key").

Track user sessions

Adding user information to your RUM sessions makes it easy to:

  • Follow the journey of a given user
  • Know which users are the most impacted by errors
  • Monitor performance for your most important users

The following attributes are optional, you should provide at least one of them:

AttributeTypeDescription
usr.idStringUnique user identifier.
usr.nameStringUser friendly name, displayed by default in the RUM UI.
usr.emailStringUser email, displayed in the RUM UI if the user name is not present. It is also used to fetch Gravatars.

To identify user sessions, use the setUserInfo(id:name:email:) API, for example:

Datadog.setUserInfo(id: "1234", name: "John Doe", email: "john@doe.com")

Initialization Parameters

You can use the following methods in Datadog.Configuration.Builder when creating the Datadog configuration to initialize the library:

set(endpoint: DatadogEndpoint)
Sets the Datadog server endpoint that data is sent to.
set(batchSize: BatchSize)
Sets the preferred size of batched data uploaded to Datadog. This value impacts the size and number of requests performed by the SDK (small batches mean more requests, but each will be smaller in size). Available values are: .small, .medium and .large.
set(uploadFrequency: UploadFrequency)
Sets the preferred frequency of uploading data to Datadog. Available values are: .frequent, .average and .rare.

RUM configuration

enableRUM(_ enabled: Bool)
Enables or disables the RUM feature.
set(rumSessionsSamplingRate: Float)
Sets the sampling rate for RUM sessions. The rumSessionsSamplingRate value must be between 0.0 and 100.0. A value of 0.0 means no sessions will be sent, 100.0 means all sessions will be sent to Datadog. If not configured, the default value of 100.0 is used.
trackUIKitRUMViews(using predicate: UIKitRUMViewsPredicate)
Enables tracking UIViewControllers as RUM views. You can use default implementation of predicate by calling this API with no parameter (trackUIKitRUMViews()) or implement your own UIKitRUMViewsPredicate customized for your app.
trackUIKitActions(_ enabled: Bool)
Enables tracking user interactions (taps) as RUM actions.
trackURLSession(firstPartyHosts: Set<String>)
Enables tracking URLSession tasks (network requests) as RUM resources. The firstPartyHosts parameter defines hosts that are categorized as first-party resources (if RUM is enabled) and have tracing information injected (if tracing feature is enabled).
setRUMViewEventMapper(_ mapper: @escaping (RUMViewEvent) -> RUMViewEvent)
Sets the data scrubbing callback for views. This can be used to modify view events before they are sent to Datadog - see Modify or drop RUM events for more.
setRUMResourceEventMapper(_ mapper: @escaping (RUMResourceEvent) -> RUMResourceEvent?)
Sets the data scrubbing callback for resources. This can be used to modify or drop resource events before they are sent to Datadog - see Modify or drop RUM events for more.
setRUMActionEventMapper(_ mapper: @escaping (RUMActionEvent) -> RUMActionEvent?)
Sets the data scrubbing callback for actions. This can be used to modify or drop action events before they are sent to Datadog - see Modify or drop RUM events for more.
setRUMErrorEventMapper(_ mapper: @escaping (RUMErrorEvent) -> RUMErrorEvent?)
Sets the data scrubbing callback for errors. This can be used to modify or drop error events before they are sent to Datadog - see Modify or drop RUM events for more.
setRUMResourceAttributesProvider(_ provider: @escaping (URLRequest, URLResponse?, Data?, Error?) -> [AttributeKey: AttributeValue]?)
Sets a closure to provide custom attributes for intercepted resources. The provider closure is called for each resource collected by the SDK. This closure is called with task information and may return custom resource attributes or nil if no attributes should be attached.

Logging configuration

enableLogging(_ enabled: Bool)
Enables or disables the Logging feature.

Tracing configuration

enableTracing(_ enabled: Bool)
Enables or disables the Tracing feature.
setSpanEventMapper(_ mapper: @escaping (SpanEvent) -> SpanEvent)
Sets the data scrubbing callback for spans. This can be used to modify or drop span events before they are sent to Datadog.

Automatically track views

To automatically track views (UIViewControllers), use the .trackUIKitRUMViews() option when configuring the SDK. By default, views are named with the view controller’s class name. To customize it, use .trackUIKitRUMViews(using: predicate) and provide your own implementation of the predicate which conforms to UIKitRUMViewsPredicate protocol:

public protocol UIKitRUMViewsPredicate {
    func rumView(for viewController: UIViewController) -> RUMView?
}

Inside the rumView(for:) implementation, your app should decide if a given UIViewController instance should start the RUM view (return value) or not (return nil). The returned RUMView value must specify the name and may provide additional attributes for created RUM view.

For instance, you can configure the predicate to use explicit type check for each view controller in your app:

class YourCustomPredicate: UIKitRUMViewsPredicate {

    func rumView(for viewController: UIViewController) -> RUMView? {
        switch viewController {
        case is HomeViewController:     return .init(name: "Home")
        case is DetailsViewController:  return .init(name: "Details")
        default:                        return nil
        }
    }
}

You can even come up with a more dynamic solution depending on your app’s architecture. For example, if your view controllers use accessibilityLabel consistently, you can name views by the value of accessibility label:

class YourCustomPredicate: UIKitRUMViewsPredicate {

    func rumView(for viewController: UIViewController) -> RUMView? {
        guard let accessibilityLabel = viewController.accessibilityLabel else {
            return nil
        }

        return RUMView(name: accessibilityLabel)
    }
}

Note: The SDK calls rumView(for:) many times while your app is running. It is recommended to keep its implementation fast and single-threaded.

Automatically track user actions

To automatically track user tap actions, use the .trackUIKitActions() option when configuring the SDK.

Automatically track network requests

To automatically track resources (network requests) and get their timing information such as time to first byte or DNS resolution, use the .trackURLSession() option when configuring the SDK and set DDURLSessionDelegate for the URLSession that you want to monitor:

let session = URLSession(
    configuration: .default,
    delegate: DDURLSessionDelegate(),
    delegateQueue: nil
)

Also, you can configure first party hosts using .trackURLSession(firstPartyHosts:). This will classify resources matching given domain as “first party” in RUM and will propagate tracing information to your backend (if Tracing feature is enabled).

For instance, you can configure example.com as first party host and enable both RUM and Tracing features:

Datadog.initialize(
    // ...
    configuration: Datadog.Configuration
        .builderUsing(/* ... */)
        .trackUIKitRUMViews()
        .trackURLSession(firstPartyHosts: ["example.com"])
        .build()
)

Global.rum = RUMMonitor.initialize()
Global.sharedTracer = Tracer.initialize()

let session = URLSession(
    configuration: .default,
    delegate: DDURLSessionDelegate(),
    delegateQueue: nil
)

This tracks all requests sent with the instrumented session. Requests matching the example.com domain are marked as “first party” and tracing information is sent to your backend to connect the RUM resource with its Trace.

To add custom attributes to resources, use the .setRUMResourceAttributesProvider(_ :) option when configuring SDK. By setting attributes provider closure, you can return additional attributes to be attached to tracked resource.

For instance, you may want to add HTTP request and response headers to the RUM resource:

.setRUMResourceAttributesProvider { request, response, data, error in
    return [
        "request.headers" : redactedHeaders(from: request),
        "response.headers" : redactedHeaders(from: response)
    ]
}

Automatically track errors

All “error” and “critical” logs sent with Logger are automatically reported as RUM errors and linked to the current RUM view:

let logger = Logger.builder.build()

logger.error("message")
logger.critical("message")

Similarly, all finished spans marked as error are reported as RUM errors:

let span = Global.sharedTracer.startSpan(operationName: "operation")
// ... capture the `error`
span.setError(error)
span.finish()

Modify or drop RUM events

To modify attributes of a RUM event before it is sent to Datadog or to drop an event entirely, use the event mappers API when configuring the SDK:

Datadog.Configuration
    .builderUsing(...)
    .setRUMViewEventMapper { viewEvent in 
        return viewEvent
    }
    .setRUMErrorEventMapper { errorEvent in
        return errorEvent
    }
    .setRUMResourceEventMapper { resourceEvent in
        return resourceEvent
    }
    .setRUMActionEventMapper { actionEvent in
        return actionEvent
    }
    .build()

Each mapper is a Swift closure with a signature of (T) -> T?, where T is a concrete RUM event type. This allows changing portions of the event before it is sent. For example, to redact sensitive information in RUM Resource’s url, implement a custom redacted(_:) -> String function and use it in RUMResourceEventMapper:

.setRUMResourceEventMapper { resourceEvent in
    var resourceEvent = resourceEvent
    resourceEvent.resource.url = redacted(resourceEvent.resource.url)
    return resourceEvent
}

Returning nil from the error, resource, or action mapper drops the event entirely (it won’t be sent to Datadog). The value returned from the view event mapper must be not nil (to drop views customize your implementation of UIKitRUMViewsPredicate. Read more in tracking views automatically).

Depending on the event’s type, only some specific properties can be modified:

Event TypeAttribute keyDescription
RUMViewEventviewEvent.view.nameName of the view
viewEvent.view.urlURL of the view
RUMActionEventactionEvent.action.target?.nameName of the action
actionEvent.view.urlURL of the view linked to this action
RUMErrorEventerrorEvent.error.messageError message
errorEvent.error.stackStacktrace of the error
errorEvent.error.resource?.urlURL of the resource the error refers to
errorEvent.view.urlURL of the view linked to this error
RUMResourceEventresourceEvent.resource.urlURL of the resource
resourceEvent.view.urlURL of the view linked to this resource

To be compliant with the GDPR regulation, the SDK requires the tracking consent value at initialization. The trackingConsent setting can be one of the following values:

  1. .pending - the SDK starts collecting and batching the data but does not send it to Datadog. The SDK waits for the new tracking consent value to decide what to do with the batched data.
  2. .granted - the SDK starts collecting the data and sends it to Datadog.
  3. .notGranted - the SDK does not collect any data: logs, traces, and RUM events are not sent to Datadog.

To change the tracking consent value after the SDK is initialized, use the Datadog.set(trackingConsent:) API call. The SDK changes its behavior according to the new value. For example, if the current tracking consent is .pending:

  • if changed to .granted, the SDK will send all current and future data to Datadog;
  • if changed to .notGranted, the SDK will wipe all current data and will not collect any future data.

Sample RUM sessions

To control the data your application sends to Datadog RUM, you can specify a sampling rate for RUM sessions while initializing the SDK as a percentage between 0 and 100.

For instance, to only keep 50% of sessions use:

Datadog.initialize(
    // ...
    configuration: Datadog.Configuration
        .builderUsing(/* ... */)
        .set(rumSessionsSamplingRate: 50.0)
        // ...
        .build()
)

Sending data when device is offline

RUM ensures availability of data when your user device is offline. In cases of low-network areas, or when the device battery is too low, all the RUM events are first stored on the local device in batches. They are sent as soon as the network is available, and the battery is high enough to ensure the SDK does not impact the end user’s experience. If the network is not available while your application is in the foreground, or if an upload of data fails, the batch is kept until it can be sent successfully.

This means that even if users open your application while offline, no data is lost.

Note: The data on the disk is automatically discarded if it gets too old to ensure the SDK doesn’t use too much disk space.

Configuring a custom proxy for Datadog data upload

If your app is running on devices behind a custom proxy, you can let the SDK’s data uploader know about it to ensure all tracked data are uploaded with the relevant configuration. You can specify your proxy configuration (as described in the URLSessionConfiguration.connectionProxyDictionary documentation) when initializing the SDK.

Datadog.initialize(
    // ...
    configuration: Datadog.Configuration
        .builderUsing(/* ... */)
        .set(proxyConfiguration: [
            kCFNetworkProxiesHTTPEnable: true, 
            kCFNetworkProxiesHTTPPort: 123, 
            kCFNetworkProxiesHTTPProxy: "www.example.com", 
            kCFProxyUsernameKey: "proxyuser", 
            kCFProxyPasswordKey: "proxypass" 
        ])
        // ...
        .build()
)

Further Reading

Additional helpful documentation, links, and articles: