The Atatus iOS SDK reports network requests as RUM resources, attributed to the view that issued them. Because it instruments URLSession itself, any client built on URLSession is covered, including Alamofire, Apollo, SDWebImage, and generated OpenAPI clients.

Prerequisites

Report requests as resources

Resource tracking is off until you set urlSessionTracking on the RUM configuration. Setting it to an empty configuration is enough:

copy
icon/buttons/copy
import AtatusRUM

RUM.enable(
    with: RUM.Configuration(
        uiKitViewsPredicate: DefaultUIKitRUMViewsPredicate(),
        urlSessionTracking: .init()
    )
)

Every request then reports its URL, method, status code, duration, and response size, correlated with the active view. This covers URLSession.shared, sessions you create yourself, completion handler APIs, and async/await calls, with no change to your request code.

Note:

Unlike other SDKs you may have used, you do not have to supply a URLSessionDelegate to get resources. A delegate is only needed for the timing breakdown described below.

Add the duration breakdown

To see where a request spent its time, which means DNS, connect, TLS, time to first byte, and download, register a delegate class of your own. It can be empty, because the SDK adds the delegate methods it needs:

copy
icon/buttons/copy
import AtatusCore
import AtatusRUM

final class AppSessionDelegate: NSObject, URLSessionDataDelegate {}

// Call this after RUM.enable()
URLSessionInstrumentation.enableDurationBreakdown(
    with: .init(delegateClass: AppSessionDelegate.self)
)

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

Requests made through that session carry the full breakdown. Requests through other sessions still report as resources, just without it.

Warning:

Register the class with enableDurationBreakdown, then hand URLSession an instance of that same class. A session built with a different delegate type gets no breakdown. On iOS 15 and later, the per-task delegate: parameter works too.

To stop instrumenting a delegate class, for example in a test:

copy
icon/buttons/copy
URLSessionInstrumentation.disable(delegateClass: AppSessionDelegate.self)

Connect resources to backend traces

List the hosts you own in firstPartyHostsTracing so the SDK injects trace headers and records the trace and span IDs on the resource:

copy
icon/buttons/copy
RUM.enable(
    with: RUM.Configuration(
        urlSessionTracking: .init(
            firstPartyHostsTracing: .trace(
                hosts: ["api.example.com"],
                sampleRate: 20
            )
        )
    )
)

A host matches its subdomains, so example.com also covers api.example.com. See Trace Collection for the headers that are injected, the supported formats, and what the backend needs.

Note:

firstPartyHostsTracing controls trace propagation only. Resources are reported for every host either way, so there is no need to list a third-party API you do not instrument.

Capture request and response headers

Header capture is off by default. Turning on the defaults records the headers that explain caching and content negotiation:

copy
icon/buttons/copy
RUM.enable(
    with: RUM.Configuration(
        urlSessionTracking: .init(trackResourceHeaders: .defaults)
    )
)

The default set is:

Direction Headers
Request cache-control, content-type
Response cache-control, content-encoding, content-length, content-type, etag, age, expires, vary, server-timing, x-cache, cf-cache-status, x-vercel-cache

To add headers of your own, list them by name. Matching ignores case, and the names apply to both the request and the response:

copy
icon/buttons/copy
RUM.enable(
    with: RUM.Configuration(
        urlSessionTracking: .init(
            trackResourceHeaders: .custom([
                .defaults,
                .matchHeaders(["x-request-id", "x-tenant"])
            ])
        )
    )
)
Warning:

Credentials are never captured, whatever you ask for. The SDK always drops authorization, www-authenticate, proxy-authorization, proxy-authenticate, connection, host, and content-length from requests, and drops any header whose name contains token, cookie, secret, password, credential, bearer, an API or access key, or a forwarding or client IP header. Do not rely on that as your only safeguard: review what you add.

Captured headers are truncated to 128 bytes per value, capped at 100 headers and 2 KB per direction, and lowercased. The default headers are kept first, so they survive the budget before your custom ones.

Attach your own attributes to a resource

A resource attributes provider runs once per request and returns the attributes to attach:

copy
icon/buttons/copy
RUM.enable(
    with: RUM.Configuration(
        urlSessionTracking: .init(
            resourceAttributesProvider: { request, response, data, error in
                var attributes: [String: Encodable] = [:]

                if let endpoint = request.value(forHTTPHeaderField: "x-endpoint-name") {
                    attributes["endpoint"] = endpoint
                }
                if let error = error {
                    attributes["error.description"] = String(describing: error)
                }

                return attributes
            }
        )
    )
)

The closure runs on a background thread, so keep it quick and free of side effects. Be aware of what data contains:

  • It is nil for async/await requests.
  • It is nil for images, video, audio, and binary downloads.
  • It is nil when the body is larger than 512 KB, so the SDK does not hold a large response in memory.

Drop resources you do not want

Health checks and polling endpoints add volume without adding insight. Drop them with a resource event mapper:

copy
icon/buttons/copy
RUM.enable(
    with: RUM.Configuration(
        urlSessionTracking: .init(),
        resourceEventMapper: { event in
            if event.resource.url.contains("/health") {
                return nil
            }
            return event
        }
    )
)

Returning nil drops the resource. Returning a modified event is how you redact a URL that embeds an identifier or a token.

Track a request manually

For a client that does not use URLSession, report the request yourself. Start it with a key that is unique among the requests in flight, then stop it with the same key:

copy
icon/buttons/copy
import AtatusRUM

let monitor = RUMMonitor.shared()
let key = UUID().uuidString

monitor.startResource(
    resourceKey: key,
    httpMethod: .get,
    urlString: "https://api.example.com/v2/products"
)

// On success
monitor.stopResource(resourceKey: key, statusCode: 200, kind: .fetch, size: 1_024)

// On failure
monitor.stopResourceWithError(resourceKey: key, error: error)

The resource kinds are document, xhr, beacon, fetch, css, js, image, font, media, other, and native. If you have a URLResponse, pass it instead of the status code and kind, and if you have URLSessionTaskMetrics, add them with addResourceMetrics(resourceKey:metrics:) before stopping the resource.

Note:

A manually tracked resource carries no trace context, so it cannot be linked to a backend trace. To correlate a non-URLSession client with your backend, create a span and inject its headers yourself. See Trace Collection.

Report GraphQL operations

GraphQL requests all share one URL, which makes them indistinguishable in a dashboard. Pass the operation details as request headers and the SDK turns them into resource attributes, removing the headers before the request leaves the device:

Header Value
_atatus-custom-header-graph-ql-operation-name Operation name, such as GetCart
_atatus-custom-header-graph-ql-operation-type query, mutation, or subscription
_atatus-custom-header-graph-ql-variables Variables, as a JSON string
_atatus-custom-header-graph-ql-payload The operation payload

With an Apollo client, set them in an interceptor. The SDK also extracts the errors array from a GraphQL response, which is how a request that returns HTTP 200 with errors still shows up as a failure. That extraction needs the response body, so it works for completion handler requests and for instrumented delegates under the 512 KB limit.

Other clients

Client What you do
Alamofire Nothing. It runs on URLSession. For the duration breakdown, register Alamofire.SessionDelegate.self as the delegate class
SDWebImage Nothing. Image requests are reported as resources
Generated OpenAPI clients Nothing, as long as they use URLSession
Apollo GraphQL Set the GraphQL headers above to label the operation

Objective-C

Resource tracking is configured on ATRUMConfiguration, and the duration breakdown through ATURLSessionInstrumentation:

copy
icon/buttons/copy
@import AtatusRUM;

[ATURLSessionInstrumentation enableDurationBreakdownWith:
    [[ATURLSessionInstrumentationConfiguration alloc] initWithDelegateClass:[AppSessionDelegate class]]];

Next steps