The Atatus mobile SDK collects distributed traces from native Android and Android TV builds. It instruments outbound network requests, exposes an API for spanning arbitrary operations, and propagates trace context to instrumented backend services so a mobile request and its server-side work appear on one trace. The tracer implements the OpenTelemetry standard.

Prerequisites

  • The Atatus SDK initialized in your application. See Android Monitoring Setup.
  • A RUM application in Atatus, which provisions the license key the SDK authenticates with.
Note:

If your minSdkVersion is below 26, enable Java 8+ library desugaring in your Gradle configuration. The tracer depends on java.time APIs that are unavailable on earlier API levels without it.

Setup

1. Add the trace dependencies

Declare the trace library and the OkHttp integration in your application module's build script:

copy
icon/buttons/copy
dependencies {
    implementation("com.atatus:atatus-sdk-android-trace:1.0.1")
    implementation("com.atatus:atatus-sdk-android-okhttp:1.0.1")
}

2. Initialize the SDK

Traces share the core SDK instance with RUM, so initialization is identical to Android Monitoring Setup. Skip this step if the SDK is already initialized in your Application class:

copy
icon/buttons/copy
import android.app.Application
import com.atatus.android.Atatus
import com.atatus.android.core.configuration.Configuration
import com.atatus.android.privacy.TrackingConsent

class SampleApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        val configuration = Configuration.Builder(
            licenseKey = "<LICENSE_KEY>",
            env = "<ENV_NAME>",
            variant = "<APP_VARIANT_NAME>"
        )
            .build()

        Atatus.initialize(this, configuration, TrackingConsent.GRANTED)
    }
}

3. Enable the trace feature

Enabling the feature registers the trace pipeline. Registering a global tracer then makes it reachable from anywhere in your code without passing an instance through your call graph:

copy
icon/buttons/copy
import com.atatus.android.trace.AtatusTracing
import com.atatus.android.trace.GlobalAtatusTracer
import com.atatus.android.trace.Trace
import com.atatus.android.trace.TraceConfiguration

val traceConfiguration = TraceConfiguration.Builder().build()
Trace.enable(traceConfiguration)

GlobalAtatusTracer.registerIfAbsent(
    AtatusTracing.newTracerBuilder().build()
)

4. Set the sample rate

Sampling is decided at the trace level and propagates to every span beneath it, so a sampled trace is never partially recorded. Set the rate, service name, and partial flush threshold when building the tracer:

copy
icon/buttons/copy
val tracer = AtatusTracing.newTracerBuilder()
    .withPartialFlushMinSpans(10)
    .withSampleRate(80f)
    .withServiceName("<SERVICE_NAME>")
    .build()

GlobalAtatusTracer.registerIfAbsent(tracer)

5. Instrument network requests

AtatusInterceptor sits in the OkHttp interceptor chain, opens a span per request, and injects trace context headers on hosts you declare. List only the hosts you control, since the headers are meaningless to third-party services:

copy
icon/buttons/copy
import com.atatus.android.okhttp.AtatusInterceptor
import okhttp3.OkHttpClient

val tracedHosts = listOf("example.com", "example.eu")

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(
        AtatusInterceptor.Builder(tracedHosts)
            .setTraceSampleRate(20f)
            .build()
    )
    .build()

Add a network interceptor as well to capture connection-level timing, which resolves after redirects and retries rather than around them:

copy
icon/buttons/copy
import com.atatus.android.okhttp.AtatusInterceptor
import com.atatus.android.okhttp.TracingInterceptor
import okhttp3.OkHttpClient

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(
        AtatusInterceptor.Builder(tracedHosts)
            .setTraceSampleRate(20f)
            .build()
    )
    .addNetworkInterceptor(
        TracingInterceptor.Builder(tracedHosts)
            .setTraceSampleRate(100f)
            .build()
    )
    .build()

Each instrumented request is reported as a span. When the host appears in tracedHosts and the receiving service runs Atatus APM, the propagated context joins the mobile span to its backend trace.

6. Create custom spans

Resolve the global tracer and wrap any operation whose latency you want measured:

copy
icon/buttons/copy
import com.atatus.android.trace.GlobalAtatusTracer

val tracer = GlobalAtatusTracer.get()

val span = tracer.buildSpan("<SPAN_NAME>").start()
// Do something
span.finish()

7. Activate and nest spans

Activating a span binds it to the current scope, so spans started inside that scope are parented to it automatically. Close the scope and finish the span in a finally block, or an early return leaves the span open:

copy
icon/buttons/copy
val tracer = GlobalAtatusTracer.get()

val span = tracer.buildSpan("parent_operation").start()
try {
    tracer.activateSpan(span).use {
        // Work that belongs to the parent span

        val childSpan = tracer.buildSpan("child_operation").start()
        try {
            tracer.activateSpan(childSpan).use {
                // Work that belongs to the child span
            }
        } catch (e: Throwable) {
            childSpan.logThrowable(e)
        } finally {
            childSpan.finish()
        }
    }
} catch (e: Throwable) {
    span.logThrowable(e)
} finally {
    span.finish()
}

8. Add tags and errors to spans

Tags add queryable dimensions to a span, and error logging marks it as failed so it surfaces in error rate aggregations:

copy
icon/buttons/copy
span.setTag("http.url", url)
span.logThrowable(throwable)
span.logErrorMessage("Something went wrong")

RUM bundling stamps each span with the active view and session, which is what lets you pivot from a slow trace to the user journey that triggered it:

copy
icon/buttons/copy
val tracer = AtatusTracing.newTracerBuilder()
    .setBundleWithRumEnabled(true)
    .build()

GlobalAtatusTracer.registerIfAbsent(tracer)

Next steps