AWS Distro for OpenTelemetry

Manual Instrumentation for Traces and RUM Events with the Android SDK

Manual Instrumentation for Traces and RUM Events with the Android SDK

Introduction

The AWS Distro for OpenTelemetry (ADOT) Android SDK can used with any Android application running on Android OS 8+ (API level 26+) to gather real user monitoring (RUM) telemetry data. The Android SDK acts as a wrapper atop the OpenTelemetry (OTel) SDK to simplify the process of instrumenting applications. It is pre-configured for compatibility with AWS CloudWatch RUM but can also be used with any other tracing backend. Out of the box, it propagates traces using W3C Trace Context.

The Android agent is the recommended way to instrument most Android applications for Traces and RUM Events. For more information, refer to the documentation on auto-instrumentation.




Requirements

Currently, the Android SDK is only officially supported on applications running on Android 8.0 Oreo (API version 26) or greater. Java 8 (or later) and Kotlin 1.8.0 (or later) is required to build the library.




Installation

The ADOT Android SDK is published to Maven Central as the core artifact. You should add the dependency to your application's build.gradle . For example (using Kotlin DSL):

1plugins {
2 id("com.android.application")
3 id("org.jetbrains.kotlin.android")
4}
5
6dependencies {
7 // ADOT Android Core - for manual instrumentation
8 implementation("software.amazon.opentelemetry.android:core:1.0.0")
9
10 // Optional: Auth module for Sigv4 authentication
11 implementation("software.amazon.opentelemetry.android:kotlin-sdk-auth:1.0.0")
12
13 // Automated HTTP client instrumentation with ByteBuddy (optional but recommended)
14 byteBuddy("io.opentelemetry.android.instrumentation:okhttp3-agent:0.15.0-alpha")
15 byteBuddy("io.opentelemetry.android.instrumentation:httpurlconnection-agent:0.15.0-alpha")
16}

Adding the core module as a dependency requires you to manually initialize the SDK in your application code. Next, we'll discuss how to initialize and configure the SDK.




Initializing the SDK in your application

In your application, preferably in Application.onCreate(), you will need to initialize the SDK. At minimum, this requires the following:

1import software.amazon.opentelemetry.android.OpenTelemetryRumClient
2
3class MyApplication : Application() {
4 override fun onCreate() {
5 super.onCreate()
6
7 OpenTelemetryRumClient {
8 androidApplication = this@MyApplication
9 }
10 }
11}

This will initialize the OTel SDKs with the pre-enabled suite of default instrumentations on your provided androidApplication. Next, you will configure the SDK to point the telemetry somewhere.

NOTE: If you don't already have an Application class defined, you will need to register this new MyApplication class in your Android manifest. You can find that in your AndroidManifest.xml file. For example:

1<application>
2 android:name="com.example.MyApplication"
3
4 <!-- All other attributes, activities, etc -->
5
6 android:label="MyApplication"
7
8</application>



Configuring the SDK with AWS CloudWatch RUM (with resource-based policies)

ADOT Android is pre-configured to work with AWS CloudWatch RUM. To export RUM telemetry to CloudWatch RUM, you will need to first create an app monitor with resource-based policies. Next, modify your code with the following:

1import io.opentelemetry.sdk.resources.Resource
2import software.amazon.opentelemetry.android.OpenTelemetryRumClient
3
4class MyApplication : Application() {
5 override fun onCreate() {
6 super.onCreate()
7
8 OpenTelemetryRumClient {
9 androidApplication = this@MyApplication
10
11 // point telemetry to AWS RUM
12 awsRum {
13 region = "<your region>"
14 appMonitorId = "<your app monitor id>"
15
16 // optional, if you have a resource-based policy with an alias
17 alias = "<your rum alias>"
18 }
19
20 // customize your OTel Resource
21 otelResource = Resource.builder()
22 .put("service.name", "MyApplication")
23 .put("service.namespace", "MyTeam")
24 .put("service.version", "1.0.0")
25 .put("deployment.environment", "production")
26 .build()
27 }
28 }
29}

That's it! This is the minimum configuration to begin exporting RUM telemetry to CloudWatch RUM. From there, you can use CloudWatch RUM and CloudWatch Application Signals to monitor your application health and track long-term application performance against your business objectives.

Application Signals provides you with a unified, application-centric view of your applications, services, and dependencies, and helps you monitor and triage application health.

Get started with CloudWatch Application Signals

Use resource-based policies with CloudWatch RUM




Configuring the SDK with other OTLP endpoints

To export to a third-party OTLP endpoint that is not yet supported in the Android SDK out of the box, you can configure custom Span and LogRecord exporters:

1import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
2import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter
3import io.opentelemetry.sdk.resources.Resource
4
5class MyApplication : Application() {
6 override fun onCreate() {
7 super.onCreate()
8
9 OpenTelemetryRumClient {
10 androidApplication = this@MyApplication
11
12 // Configure custom OTLP endpoints
13 spanExporter = OtlpHttpSpanExporter.builder()
14 .setEndpoint("https://your-collector.example.com/v1/traces")
15 .build()
16
17 logRecordExporter = OtlpHttpLogRecordExporter.builder()
18 .setEndpoint("https://your-collector.example.com/v1/logs")
19 .build()
20
21 otelResource = Resource.builder()
22 .put("service.name", "MyApplication")
23 .put("service.version", "1.0.0")
24 .build()
25 }
26 }
27}



Adding custom headers

If your OTLP endpoint requires authentication or custom headers, you can supply those yourself. For example:

1spanExporter = OtlpHttpSpanExporter.builder()
2 .setEndpoint("https://your-collector.example.com/v1/traces")
3 .setHeaders(mapOf(
4 "Authorization" to "Bearer your-token",
5 "X-Custom-Header" to "value"
6 ))
7 .build()



Exporting with Sigv4 headers

To simplify the process of exporting telemetry via OTLP with Sigv4 signed requests, the Android SDK ships with an optional kotlin-sdk-auth library. You are responsible for sourcing your own credentials in a secure manner. An example implementation could look like:

1import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
2import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
3import io.opentelemetry.sdk.resources.Resource
4import software.amazon.opentelemetry.android.OpenTelemetryRumClient
5import software.amazon.opentelemetry.android.auth.AwsSigV4SpanExporter
6import software.amazon.opentelemetry.android.auth.AwsSigV4LogRecordExporter
7
8class MyApplication : Application() {
9 override fun onCreate() {
10 super.onCreate()
11
12 // Configure your AWS credentials provider
13 val credentialsProvider = CredentialsProvider {
14 Credentials(
15 accessKeyId = "your-access-key-id",
16 secretAccessKey = "your-secret-access-key"
17 sessionToken = "your-session-token"
18 )
19 }
20
21 OpenTelemetryRumClient {
22 androidApplication = this@MyApplication
23
24 // Configure SigV4-signed exporters for RUM data plane
25 spanExporter = AwsSigV4SpanExporter.builder()
26 .setEndpoint("https://dataplane.rum.us-east-1.amazonaws.com/v1/rum")
27 .setRegion("us-east-1")
28 .setServiceName("rum")
29 .setCredentialsProvider(credentialsProvider)
30 .build()
31
32 logRecordExporter = AwsSigV4LogRecordExporter.builder()
33 .setEndpoint("https://dataplane.rum.us-east-1.amazonaws.com/v1/rum")
34 .setRegion("us-east-1")
35 .setServiceName("rum")
36 .setCredentialsProvider(credentialsProvider)
37 .build()
38
39 otelResource = Resource.builder()
40 .put("service.name", "MyApplication")
41 .put("service.version", "1.0.0")
42 .build()
43 }
44 }
45}

NOTE: Vending your application with static credentials in a production environment carries inherent risk. One possible solution is to source your credentials from Amazon Cognito using the AWS SDK for Kotlin.




Advanced customization of the SDK

By default, the SDK enables all available telemetry types. You can selectively enable or disable specific telemetry types to control what RUM data is collected from your application.

1import io.opentelemetry.sdk.resources.Resource
2import io.opentelemetry.sdk.trace.samplers.Sampler
3import software.amazon.opentelemetry.android.OpenTelemetryRumClient
4import software.amazon.opentelemetry.android.TelemetryConfig
5
6class MyApplication : Application() {
7 override fun onCreate() {
8 super.onCreate()
9
10 OpenTelemetryRumClient {
11 androidApplication = this@MyApplication
12
13 awsRum {
14 region = "us-east-1"
15 appMonitorId = "your-app-monitor-id"
16 }
17
18 // Customize telemetry types
19 telemetry = listOf(
20 TelemetryConfig.ACTIVITY, // Activity lifecycle spans
21 TelemetryConfig.FRAGMENT, // Fragment lifecycle spans
22 TelemetryConfig.ANR, // ANR detection
23 TelemetryConfig.CRASH, // Crash reporting
24 TelemetryConfig.NETWORK, // Network state monitoring
25 TelemetryConfig.SLOW_RENDERING, // Slow rendering detection
26 TelemetryConfig.STARTUP, // App startup monitoring
27 TelemetryConfig.HTTP_URLCONNECTION, // HttpURLConnection instrumentation
28 TelemetryConfig.OKHTTP_3, // OkHttp3 instrumentation
29 TelemetryConfig.UI_LOADING, // UI load time (TTFD)
30 TelemetryConfig.SESSION_EVENTS // Session start/end events
31 )
32
33 otelResource = Resource.builder()
34 .put("service.name", "MyApplication")
35 .build()
36
37 tracerSampler = Sampler.create(0.1) // sample 10% of traces
38 }
39 }
40}

For more advanced configuration options, refer to the ADOT Android GitHub repository.