Distributed tracing links a request made by your Flutter application to the trace it produced on your backend. A slow screen in RUM then leads straight to the service that made it slow.

This page covers choosing which hosts are traced, which headers are sent, and how sampling works.

Note: This guide covers Android targets only. iOS instrumentation is not part of these guides.

Before you start

You need two things:

List your first party hosts

Tracing headers are only added to hosts you name. Set firstPartyHosts on your configuration:

copy
icon/buttons/copy
final configuration = AtatusConfiguration(
  licenseKey: '<LICENSE_KEY>',
  env: '<ENV_NAME>',
  appName: '<APP_NAME>',
  firstPartyHosts: ['api.example.com', 'payments.example.com'],
)..enableHttpTracking();

A host matches itself and all of its subdomains, so api.example.com also matches beta.api.example.com. Wildcards, schemes, paths, and regular expressions are not supported. Use bare domain names.

Warning:

Every host you list receives tracing headers on every request. Listing a third party domain sends your trace IDs to that third party, and can cause CORS preflight failures on services that do not expect the headers. List only hosts you control.

Requests to hosts that are not listed are still reported as RUM resources. They just do not carry tracing headers, so they cannot be joined to a backend trace.

Choose which headers are sent

By default, each host in firstPartyHosts receives two header formats: the Atatus headers and W3C trace context. That covers both an Atatus backend agent and any service that reads standard traceparent headers.

To control the formats per host, use firstPartyHostsWithTracingHeaders instead:

copy
icon/buttons/copy
final configuration = AtatusConfiguration(
  licenseKey: '<LICENSE_KEY>',
  env: '<ENV_NAME>',
  appName: '<APP_NAME>',
  firstPartyHostsWithTracingHeaders: {
    'api.example.com': {TracingHeaderType.atatus, TracingHeaderType.tracecontext},
    'legacy.example.com': {TracingHeaderType.b3multi},
  },
)..enableHttpTracking();

These are the four formats and the headers each one writes:

TracingHeaderType Headers sent
atatus x-atatus-trace-id, x-atatus-parent-id, x-atatus-origin, x-atatus-sampling-priority, and baggage
tracecontext traceparent, tracestate, and baggage
b3 A single b3 header, formatted {traceId}-{spanId}-{sampled}
b3multi X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId, and X-B3-Sampled

The baggage header carries the RUM session ID, and the user and account IDs when you have set them, so your backend can attribute a trace to the same person.

Note:

Setting firstPartyHosts replaces anything already in firstPartyHostsWithTracingHeaders. Use one or the other, not both.

Control how much is traced

Two options decide whether a request carries tracing headers:

Option Default Purpose
traceSampleRate 100.0 Percentage of tracked resources that carry tracing headers, from 0.0 to 100.0
traceContextInjection TraceContextInjection.sampled Whether headers are written for unsampled requests as well as sampled ones

Set them on your RUM configuration:

copy
icon/buttons/copy
rumConfiguration: AtatusRumConfiguration(
  traceSampleRate: 20.0,
  traceContextInjection: TraceContextInjection.sampled,
),

Sampling is deterministic. The same trace produces the same decision every time, so a trace is never half recorded across services.

traceContextInjection decides what happens to the requests sampling dropped:

  • sampled, the default, writes no tracing headers on a dropped request.
  • all still writes the headers, with the sampling flag set to 0. Your backend then sees the trace and can apply its own sampling decision.
Tip:

Use all when your backend decides sampling. The frontend then reports every request to the backend without forcing its own decision on it, which keeps the two ends of a trace consistent.

Group requests into one trace

By default each request is its own trace. To tie several requests together as one operation, start a trace, make the requests, then stop it:

copy
icon/buttons/copy
AtatusSdk.instance.rum?.startTrace();

await loadProfile();
await loadOrders();
await loadRecommendations();

AtatusSdk.instance.rum?.stopTrace();

Every request made while the trace is active joins it. The first request becomes the root, and each later request is attached as a child of the one before it, so the backend can show the whole sequence as one tree.

Note:

Always call stopTrace(). A trace left open keeps attaching later requests to it, including ones from a different screen.

Check whether a host is traced

Two helpers let you check the configuration at runtime, which is useful when debugging a request that is not joining its trace:

copy
icon/buttons/copy
final uri = Uri.parse('https://api.example.com/orders');

// Is this host in firstPartyHosts?
final tracked = AtatusSdk.instance.isFirstPartyHost(uri);

// Which header formats will it receive?
final headerTypes = AtatusSdk.instance.headerTypesForHost(uri);

What lands on the resource

A traced resource carries these attributes, which are what the backend uses to join the two sides:

Attribute Description
_atatus.trace_id The 128 bit trace ID shared with the backend trace
_atatus.span_id The span ID for this request
_atatus.parent_span_id The parent span, set when the request is part of a trace you started
_atatus.rule_psr The trace sample rate that was applied
_atatus.span.kind Set to client when the request has a parent span

A request that sampling dropped keeps _atatus.rule_psr but carries no trace or span ID.

Troubleshooting

If a request is reported as a resource but never joins a backend trace, work through these in order:

  1. Confirm the host is listed in firstPartyHosts, exactly, with no scheme or path. Check it with isFirstPartyHost.
  2. Raise traceSampleRate to 100.0 while testing, so sampling is not hiding the problem.
  3. Confirm the backend service is instrumented and reads one of the header formats you send.
  4. Check that your own HttpOverrides are installed before Atatus is initialized. See the warning in Flutter Network Tracking.

Next steps