Web request performance monitoring

  • Latest Dynatrace
  • Explanation

The OneAgent for Android provides comprehensive web request monitoring capabilities

  • Automatic instrumentation—automatically captures all web requests made via HttpUrlConnection or OkHttp.
  • W3C Trace Context—enables distributed tracing 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 Android automatically instruments web requests made using Android's built-in HttpUrlConnection API and stores them as events in Grail. The same is true for requests made with the widely used OkHttp library.

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.

What is captured automatically

An exact overview of the reported data and its structure can be found in the Semantic Dictionary. The following list provides an overview of data that is automatically monitored by OneAgent:

  • Request URL.
  • HTTP method (GET, POST, PUT, DELETE, etc.).
  • Response status code.
  • Request duration.
  • Exception (if the request fails).

Supported HTTP frameworks

Automatic instrumentation works with the following:

If your web request library is based on one of these supported frameworks, the internal classes of the library are automatically instrumented. For example, Retrofit version 2 is based on OkHttp, so all Retrofit web requests are automatically instrumented.

Limitations

  • Non-HTTP protocols are not supported.
  • Requests from unsupported frameworks must be manually instrumented.
  • Using several monitoring tools simultaneously with web request instrumentation functionality enabled isn't recommended. This can lead to compatibility problems, incorrect or invalid data reporting, and loss of monitoring information. If you choose to do so, thoroughly test to ensure the tools work together correctly.

Disable automatic instrumentation

To disable automatic web request instrumentation, set the webRequests.enabled flag to false in the Dynatrace configurations block of your app's build.gradle file:

dynatrace {
configurations {
sampleConfig {
webRequests.enabled false
}
}
}

W3C Trace Context (distributed tracing)

The SDK supports W3C Trace Context (external) for distributed tracing, allowing you to correlate mobile requests with backend services instrumented by Dynatrace.

For background and examples, see W3C Trace Context.

How it works

When automatic web request instrumentation is enabled, OneAgent may propagate W3C trace context on outgoing requests by setting the following headers:

  • traceparent—identifies the request as part of a distributed trace.
  • tracestate—carries vendor-specific context. Dynatrace adds Dynatrace-specific information here.

OneAgent sets these headers automatically as part of its web request instrumentation.

W3C Trace Context headers (traceparent / tracestate) are used for distributed tracing correlation across services.

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.

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) to create or enrich trace context according to the official W3C Trace Context specification:

  • If traceparent is null, 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 null and you must not change the request headers

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

val request = Request.Builder()
.url("https://api.example.com/data")
.build()
// Read existing headers (if any)
val existingTraceparent = request.header("traceparent")
val existingTracestate = request.header("tracestate")
// Generate/enrich trace context
val traceContext = Dynatrace.generateTraceContext(existingTraceparent, existingTracestate)
val traceparentForReporting: String? = traceContext?.traceparent
if (traceContext != null) {
request = request.newBuilder()
.header("traceparent", traceContext.traceparent)
.header("tracestate", traceContext.tracestate)
.build()
}
// ...execute your HTTP request here...
val requestData = HttpRequestEventData(request.url.toString(), request.method)
.withDuration(duration)
.withStatusCode(statusCode)
if (traceparentForReporting != null) {
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 HttpRequestEventData.

Basic usage

import com.dynatrace.android.agent.Dynatrace
import com.dynatrace.android.agent.HttpRequestEventData
// Create the request data with the URL and HTTP method
val requestData = HttpRequestEventData("https://api.example.com/data", "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)

Failed request

When a request fails, include the error information:

val requestData = HttpRequestEventData("https://api.example.com/data", "POST")
.withDuration(1500)
.withThrowable(exception)
Dynatrace.sendHttpRequestEvent(requestData)

Add custom properties

Add custom event properties to capture additional context:

val requestData = HttpRequestEventData(url, "POST")
.withDuration(300)
.withStatusCode(201)
.addEventProperty("event_properties.api_version", "v2")
.addEventProperty("event_properties.endpoint", "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 or response that isn't captured automatically. OneAgent for Android provides this feature currently only for requests made with the OkHttp framework. Additionally, you can also add other event properties via an EventModifier.

When you use both approaches together, first the OkHttpEventModifier and then the EventModifier is executed.

Add a web request modifier

To add a web request modifier, implement the OkHttpEventModifier interface and pass it to the Dynatrace.addHttpEventModifier method. The modifier can access the request and response objects to extract additional data and add it to the event. In case of exception, the modifier can access the request and throwable objects to extract additional data and add it to the event.

Because of the way OkHttp handles the response body, you must use peekBody() instead of body() to avoid consuming the response stream. This way, the body is still available to later be consumed by your normal response handling.

If you use body() to access the response body in the modifier, you will receive an exception when you later try to consume the body again.

val modifier: OkHttpEventModifier = object : OkHttpEventModifier {
override fun modifyEvent(request: Request, response: Response): JSONObject {
val event = JSONObject()
// Access response details
val serverTiming = response.header("Server-Timing")
if (serverTiming != null) {
event.put("event_properties.server_timing", serverTiming)
}
// Only peek response body, do not consume it
val body = response.peekBody(1000)
event.put("event_properties.body", body.toString())
return event
}
override fun modifyEvent(request: Request, throwable: Throwable): JSONObject {
val event = JSONObject()
// Add custom headers or request info as properties
val customHeader = request.header("X-Custom-Header")
if (customHeader != null) {
event.put("event_properties.custom_header", customHeader)
}
return event
}
}
Dynatrace.addHttpEventModifier(modifier)

Available context properties

OkHttpEventModifier provides callback methods for two scenarios:

  • modifyEvent(Request request, Response response)
  • modifyEvent(Request request, Throwable throwable)

The first method is called when the request is completed and the response is available. Note that the response might not be successful, for example, when the server returns a 4xx or 5xx status code.

The second method is called when the request failed with an exception.

Callback methodParameterTypeDescription
modifyEvent(request, response)requestokhttp3.RequestThe original OkHttp request object
responseokhttp3.ResponseThe OkHttp response object (use peekBody() to read body)
modifyEvent(request, throwable)requestokhttp3.RequestThe original OkHttp request object
throwableThrowableThe exception that caused the request to fail

Remove a modifier

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

Dynatrace.removeEventModifier(modifier)

Filter requests

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

val filterModifier: OkHttpEventModifier = object : OkHttpEventModifier {
override fun modifyEvent(request: Request, response: Response?): JSONObject? {
if (request.url.toString().contains("analytics.example.com")) {
return null // return null to drop event
} else {
return JSONObject()
}
}
override fun modifyEvent(request: Request?, throwable: Throwable?): JSONObject? {
if (throwable is IOException) {
val event = JSONObject()
// throwable info is automatically added
return event
} else {
// return null to drop an event
return null
}
}
}
Dynatrace.addHttpEventModifier(filterModifier)
Related tags
Digital Experience