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.
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:
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:
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:
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:
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:
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:
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:
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:
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:
span.setTag("http.url", url)
span.logThrowable(throwable)
span.logErrorMessage("Something went wrong")
9. Link traces with RUM
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:
val tracer = AtatusTracing.newTracerBuilder()
.setBundleWithRumEnabled(true)
.build()
GlobalAtatusTracer.registerIfAbsent(tracer)
Next steps
- Track views, actions, and errors with Android Monitoring Setup.
- Correlate logs with your traces using Log Collection.
- Connect mobile traces to your backend with Application Monitoring.
+1-415-800-4104