Try it free

Web request performance monitoring

  • Latest Dynatrace
  • Explanation

The OneAgent for iOS provides comprehensive web request monitoring capabilities:

  • Automatic instrumentation—automatically captures all web requests made via URLSession.
  • W3C Trace Context—activates frontend-backend linking by propagating trace context headers.
  • Manual reporting—report web requests from custom networking implementations.
  • Event enrichment—add custom properties to web request events.

Automatic web request instrumentation

The OneAgent for iOS automatically instruments web requests made using Apple's URLSession API and stores them as events in Grail.

Each event consists of well-defined key-value fields as specified in the Semantic Dictionary for user events.

This data can be queried directly in Grail using DQL.

Additionally, the timing values from the mobile side are measured.

What is captured automatically

For each web request, the SDK captures:

  • Request URL
  • HTTP method (GET, POST, PUT, DELETE, etc.)
  • Response status code
  • Request and response size (bytes sent/received)
  • Request duration
  • Errors (if the request fails)

The data captured will be structured and reported according to the fields specified in request.*.

Supported APIs

Automatic instrumentation works with:

  • URLSession.dataTask(with:completionHandler:)
  • URLSession.downloadTask(with:completionHandler:)
  • URLSession.uploadTask(with:from:completionHandler:)
  • Async/await variants (URLSession.data(from:), etc.)
  • NSURLRequest, NSURLConnection, NSURLProtocol
  • WKWebView requests
  • NSData convenience methods

Limitations

Automatic instrumentation has the following limitations:

  • Non-HTTP(S) protocols are not supported.
  • The requests of a third-party framework may not be instrumented.
  • We don't recommend using several monitoring tools simultaneously with crash reporting or web request instrumentation functionality enabled. This might cause compatibility issues, reporting of wrong or invalid information, and loss of monitoring and crash data. Nevertheless, if you decide to do so, verify via manual testing that these tools are compatible.

  • To use both Dynatrace and Firebase, do one of the following.

    • Completely deactivate Firebase Performance Monitoring.
    • Disable automatic web request instrumentation in OneAgent for iOS.

    Follow only one of the approaches above; don't perform both actions.

  • To use both Dynatrace and mPaaS, do one of the following.

    • Disable automatic web request instrumentation in OneAgent for iOS.
    • Don't use the MPNebulaAdapter framework.

    Follow only one of the approaches above; don't perform both actions.

Disable automatic instrumentation

To disable automatic web request instrumentation, set the DTXInstrumentWebRequestTiming configuration key to false in your app's Info.plist file:

<key>DTXInstrumentWebRequestTiming</key>
<false/>

You may want to disable automatic instrumentation when:

  • You need full control over which requests are reported
  • You're using a custom networking implementation exclusively

Which type of instrumentation to use

Request typeInstrumentation typeDTXInstrumentWebRequestTiming

HTTP(S)

Option A: Auto

true

Option B: Manual

false

You cannot combine automatic and manual instrumentation for the same HTTP(S) request.

W3C Trace Context for frontend-backend linking

The SDK supports the addition of the W3C trace context, which lets you link mobile requests with backend services. For details, see Frontend-backend linking using the W3C trace context.

Automatic trace context header propagation

In most cases, you don`t need to set W3C Trace Context headers yourself. When automatic web request instrumentation is enabled, OneAgent inspects each outgoing request and decides whether to generate, preserve, or leave trace context headers unchanged based on what it finds.

  • No existing headers (typical case)
    • If the request has no trace headers, OneAgent generates a new valid traceparent and adds a corresponding tracestate entry with Dynatrace specific context
    • This enables correlation between the mobile request and the service side trace. See Mobile firewall constraints
  • Existing valid traceparent header
    • If the request already contains a valid traceparent, OneAgent keeps it and updates tracestate by adding Dynatrace vendor specific information, without overwriting existing vendor entries

If the combined tracestate grows too large, OneAgent may trim entries to stay within W3C limits. Dynatrace data is preserved whenever possible.

Deactivate automatic trace context propagation

If you don't want the W3C trace context headers to be added automatically to outgoing requests, you can turn off this behavior.

  1. Go to Experience Vitals Experience Vitals > Overview.
  2. Select Mobile to view all mobile frontends.
  3. Select the frontend you want to configure.
  4. On the Settings tab, select Frontend-backend linking.
  5. Turn off Enable frontend-backend linking via W3C trace context.

Manually propagate trace context headers

If you use a custom networking stack, you can propagate W3C Trace Context headers yourself and still correlate the request with Dynatrace distributed traces.

Use Dynatrace.generateTraceContext(traceparent:tracestate:) (Swift) / generateTraceContext:tracestate: (Objective-C) to create or enrich trace context according to the official W3C Trace Context specification:

  • If traceparent is nil, OneAgent generates a new traceparent and a corresponding tracestate (including Dynatrace vendor specific information)
  • If traceparent is present and valid, OneAgent keeps it and enriches tracestate with Dynatrace vendor specific information (without overwriting existing vendor entries)
  • If the trace context can't be created (for example, invalid traceparent or capture/tagging is not allowed), the API returns nil and you must not change the request headers

You can then reuse the resulting traceparent to correlate a manually reported web request event.

import Dynatrace
var request = URLRequest(url: URL(string: "https://api.example.com/data")!)
// Read existing headers (if any)
let existingTraceparent = request.value(forHTTPHeaderField: "traceparent")
let existingTracestate = request.value(forHTTPHeaderField: "tracestate")
// Generate/enrich trace context
var traceparentForReporting: String?
if let ctx = Dynatrace.generateTraceContext(existingTraceparent, tracestate: existingTracestate) {
request.setValue(ctx.traceparent, forHTTPHeaderField: "traceparent")
request.setValue(ctx.tracestate, forHTTPHeaderField: "tracestate")
traceparentForReporting = ctx.traceparent
} else {
// No changes should be applied when context can't be created (for example, invalid traceparent)
}
// ...execute your HTTP request here...
let requestData = DTXHttpRequestEventData(url: request.url!.absoluteString, method: request.httpMethod ?? "GET")
.withDuration(duration)
.withStatusCode(statusCode)
if let traceparent = traceparentForReporting {
requestData.withTraceparentHeader(traceparent)
}
Dynatrace.sendHttpRequestEvent(requestData)
@import Dynatrace;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api.example.com/data"]];
// Read existing headers (if any)
NSString *existingTraceparent = [request valueForHTTPHeaderField:@"traceparent"];
NSString *existingTracestate = [request valueForHTTPHeaderField:@"tracestate"];
// Generate/enrich trace context
NSString *traceparentForReporting = nil;
DTXTraceContext *ctx = [Dynatrace generateTraceContext:existingTraceparent tracestate:existingTracestate];
if (ctx) {
[request setValue:ctx.traceparent forHTTPHeaderField:@"traceparent"];
[request setValue:ctx.tracestate forHTTPHeaderField:@"tracestate"];
traceparentForReporting = ctx.traceparent;
} else {
// No changes should be applied when context can't be created (for example, invalid traceparent)
}
// ...execute your HTTP request here...
DTXHttpRequestEventData *requestData = [[[[DTXHttpRequestEventData alloc]
initWithURL:request.URL.absoluteString method:request.HTTPMethod]
withDuration:@(duration)]
withStatusCode:@(statusCode)];
if (traceparentForReporting) {
[requestData withTraceparentHeader:traceparentForReporting];
}
[Dynatrace sendHttpRequestEvent:requestData];

Report web requests manually

For networking libraries not covered by automatic instrumentation, you can manually report web requests using DTXHttpRequestEventData.

Basic usage

import Dynatrace
// Create the request data with the URL and HTTP method
let requestData = DTXHttpRequestEventData(url: "https://api.example.com/data", method: "GET")
.withDuration(250) // Duration in milliseconds
.withStatusCode(200) // HTTP status code
.withBytesSent(128) // Bytes sent
.withBytesReceived(4096) // Bytes received
// Send the web request event
Dynatrace.sendHttpRequestEvent(requestData)
@import Dynatrace;
// Create the request data with the URL and HTTP method
DTXHttpRequestEventData *requestData = [[[[[[[DTXHttpRequestEventData alloc]
initWithURL:@"https://api.example.com/data" method:@"GET"]
withDuration:@250] // Duration in milliseconds
withStatusCode:@200] // HTTP status code
withBytesSent:@128] // Bytes sent
withBytesReceived:@4096]; // Bytes received
// Send the web request event
[Dynatrace sendHttpRequestEvent:requestData];

Report errors

When a request fails, include the error information:

let requestData = DTXHttpRequestEventData(url: "https://api.example.com/data", method: "POST")
.withDuration(1500)
.withError(error as NSError)
Dynatrace.sendHttpRequestEvent(requestData)
DTXHttpRequestEventData *requestData = [[[[DTXHttpRequestEventData alloc]
initWithURL:@"https://api.example.com/data" method:@"POST"]
withDuration:@1500]
withError:error];
[Dynatrace sendHttpRequestEvent:requestData];

Include trace context

To correlate manually reported requests with distributed traces, include the traceparent header:

// Report the event with the traceparent
let requestData = DTXHttpRequestEventData(url: url, method: "GET")
.withDuration(duration)
.withStatusCode(statusCode)
.withTraceparentHeader(traceparent)
Dynatrace.sendHttpRequestEvent(requestData)
// Report the event with the traceparent
DTXHttpRequestEventData *requestData = [[[[[DTXHttpRequestEventData alloc]
initWithURL:url method:@"GET"]
withDuration:duration]
withStatusCode:statusCode]
withTraceparentHeader:traceparent];
[Dynatrace sendHttpRequestEvent:requestData];

Add custom properties

Add custom event properties to capture additional context:

let requestData = DTXHttpRequestEventData(url: url, method: "POST")
.withDuration(300)
.withStatusCode(201)
.addEventProperty("event_properties.api_version", value: "v2")
.addEventProperty("event_properties.endpoint", value: "users")
Dynatrace.sendHttpRequestEvent(requestData)
DTXHttpRequestEventData *requestData = [[[[[[DTXHttpRequestEventData alloc]
initWithURL:url method:@"POST"]
withDuration:@300]
withStatusCode:@201]
addEventProperty:@"event_properties.api_version" value:@"v2"]
addEventProperty:@"event_properties.endpoint" value:@"users"];
[Dynatrace sendHttpRequestEvent:requestData];

Custom property keys must be prefixed with event_properties.—properties without this prefix will be dropped.

Event enrichment (modifiers)

Event modifiers allow you to enrich web request events with additional data before they are sent. This is useful for adding context from the request/response that isn't captured automatically.

Add a web request modifier

let subscriber = Dynatrace.addHttpEventModifier { event, context in
// Access request details from the context
if let request = context?.request {
// Add custom headers or request info as properties
if let customHeader = request.value(forHTTPHeaderField: "X-Custom-Header") {
event.fields["event_properties.custom_header"] = customHeader
}
}
// Access response details
if let httpResponse = context?.response as? HTTPURLResponse {
if let serverTiming = httpResponse.value(forHTTPHeaderField: "Server-Timing") {
event.fields["event_properties.server_timing"] = serverTiming
}
}
// Return the modified event (or nil to drop it)
return event
}
DTXModifyEventSubscriber *subscriber = [Dynatrace addHttpEventModifier:^DTXModifyableEvent *(DTXModifyableEvent *event, DTXHttpRequestEventContext *context) {
// Access request details from the context
if (context.request) {
// Add custom headers or request info as properties
NSString *customHeader = [context.request valueForHTTPHeaderField:@"X-Custom-Header"];
if (customHeader) {
event.fields[@"event_properties.custom_header"] = customHeader;
}
}
// Access response details
if ([context.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)context.response;
NSString *serverTiming = httpResponse.allHeaderFields[@"Server-Timing"];
if (serverTiming) {
event.fields[@"event_properties.server_timing"] = serverTiming;
}
}
// Return the modified event (or nil to drop it)
return event;
}];

Available context properties

The DTXHttpRequestEventContext provides access to:

PropertyTypeDescription

request

URLRequest

The original request

response

URLResponse?

The server response (if available)

responseBody

Data?

The response body data (if captured)

error

NSError?

The error (if the request failed)

Remove a modifier

When you no longer need the modifier, remove it using the subscriber reference:

Dynatrace.removeEventModifier(subscriber)
[Dynatrace removeEventModifier:subscriber];

Filter requests

Return nil from the modifier to prevent the event from being sent:

let subscriber = Dynatrace.addHttpEventModifier { event, context in
// Don't report requests to analytics endpoints
if let url = context?.request.url?.absoluteString,
url.contains("analytics.example.com") {
return nil
}
return event
}
DTXModifyEventSubscriber *subscriber = [Dynatrace addHttpEventModifier:^DTXModifyableEvent *(DTXModifyableEvent *event, DTXHttpRequestEventContext *context) {
// Don't report requests to analytics endpoints
NSString *url = context.request.URL.absoluteString;
if (url && [url containsString:@"analytics.example.com"]) {
return nil;
}
return event;
}];
Related tags
Digital Experience