---
title: Android and Android TV Custom Instrumentation using the OpenTelemetry API
description: >-
  Instrument your Android and Android TV application with the OpenTelemetry API,
  to send traces to Datadog.
breadcrumbs: >-
  Docs > APM > Application Instrumentation > Code-Based Custom Instrumentation >
  Client-Side Custom Instrumentation > Android > Android and Android TV Custom
  Instrumentation using the OpenTelemetry API
---

# Android and Android TV Custom Instrumentation using the OpenTelemetry API

{% alert level="info" %}
Unsure when to use OpenTelemetry with Datadog? Start with [Custom Instrumentation with the OpenTelemetry API](https://docs.datadoghq.com/tracing/trace_collection/custom_instrumentation/otel_instrumentation.md) to learn more.
{% /alert %}

## Overview{% #overview %}

There are a few reasons to manually instrument your applications with the OpenTelemetry API:

- You are not using Datadog [supported library instrumentation](https://docs.datadoghq.com/tracing/trace_collection/compatibility.md).
- You want to extend the `ddtrace` library's functionality.
- You need finer control over instrumenting your applications.

The `ddtrace` library provides several techniques to help you achieve these goals. The following sections demonstrate how to use the OpenTelemetry API for custom instrumentation to use with Datadog.

## Requirements and limitations{% #requirements-and-limitations %}

- You need to download the [dd-sdk-android-trace](https://github.com/DataDog/dd-sdk-android/tree/develop/features/dd-sdk-android-trace) and [dd-sdk-android-trace-otel](https://github.com/DataDog/dd-sdk-android/tree/develop/features/dd-sdk-android-trace-otel) dependencies starting with 2.11.0+

## Setup{% #setup %}

1. Add [Android Trace](https://github.com/DataDog/dd-sdk-android/tree/develop/features/dd-sdk-android-trace) and [Android Trace OpenTelemetry](https://github.com/DataDog/dd-sdk-android/tree/develop/features/dd-sdk-android-trace-otel) dependencies to your **application module's** `build.gradle` file:

```groovy
android {
    //(...)
}
dependencies {
    implementation "com.datadoghq:dd-sdk-android-trace:x.x.x"
    implementation "com.datadoghq:dd-sdk-android-trace-otel:x.x.x"
    //(...)
}
```

**Note**: If you are targeting Android API level lower than 24, enable desugaring by adding the following lines to your `build.gradle` file:

```groovy
android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
        // ...
    }

    dependencies {
        coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:[latest_version]"
        // ...
    }
}
```
Initialize Datadog SDK with your application context, tracking consent, and [Datadog client token](https://docs.datadoghq.com/account_management/api-app-keys.md#client-tokens). For security reasons, you must use a client token, not [Datadog API keys](https://docs.datadoghq.com/account_management/api-app-keys.md#api-keys), to configure Datadog SDK.
{% callout %}
# Important note for users on the following Datadog sites: app.datadoghq.com



{% tab title="Kotlin" %}

```kotlin
class SampleApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         val configuration = Configuration.Builder(
              clientToken = <CLIENT_TOKEN>,
              env = <ENV_NAME>,
              variant = <APP_VARIANT_NAME>
         ).build()
         Datadog.initialize(this, configuration, trackingConsent)
     }
 }
```

{% /tab %}

{% tab title="Java" %}

```java
public class SampleApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         Configuration configuration =
                 new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                         .build();
         Datadog.initialize(this, configuration, trackingConsent);
     }
 }
```

{% /tab %}


{% /callout %}

{% callout %}
# Important note for users on the following Datadog sites: app.datadoghq.eu



{% tab title="Kotlin" %}

```kotlin
class SampleApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         val configuration = Configuration.Builder(
               clientToken = <CLIENT_TOKEN>,
               env = <ENV_NAME>,
               variant = <APP_VARIANT_NAME>
             )
             .useSite(DatadogSite.EU1)
             .build()
         Datadog.initialize(this, configuration, trackingConsent)
     }
 }
```

{% /tab %}

{% tab title="Java" %}

```java
public class SampleApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         Configuration configuration =
                 new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                         .useSite(DatadogSite.EU1)
                         .build();
         Datadog.initialize(this, configuration, trackingConsent);
     }
 }
```

{% /tab %}


{% /callout %}

{% callout %}
# Important note for users on the following Datadog sites: us3.datadoghq.com



{% tab title="Kotlin" %}

```kotlin
class SampleApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         val configuration = Configuration.Builder(
               clientToken = <CLIENT_TOKEN>,
               env = <ENV_NAME>,
               variant = <APP_VARIANT_NAME>
             )
             .useSite(DatadogSite.US3)
             .build()
         Datadog.initialize(this, configuration, trackingConsent)
     }
 }
```

{% /tab %}

{% tab title="Java" %}

```java
public class SampleApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         Configuration configuration =
                 new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                         .useSite(DatadogSite.US3)
                         .build();
         Datadog.initialize(this, configuration, trackingConsent);
     }
 }
```

{% /tab %}


{% /callout %}

{% callout %}
# Important note for users on the following Datadog sites: us5.datadoghq.com



{% tab title="Kotlin" %}

```kotlin
class SampleApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         val configuration = Configuration.Builder(
               clientToken = <CLIENT_TOKEN>,
               env = <ENV_NAME>,
               variant = <APP_VARIANT_NAME>
             )
             .useSite(DatadogSite.US5)
             .build()
         Datadog.initialize(this, configuration, trackingConsent)
     }
 }
```

{% /tab %}

{% tab title="Java" %}

```java
public class SampleApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         Configuration configuration =
                 new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                         .useSite(DatadogSite.US5)
                         .build();
         Datadog.initialize(this, configuration, trackingConsent);
     }
 }
```

{% /tab %}


{% /callout %}

{% callout %}
# Important note for users on the following Datadog sites: app.ddog-gov.com



{% tab title="Kotlin" %}

```kotlin
class SampleApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         val configuration = Configuration.Builder(
               clientToken = <CLIENT_TOKEN>,
               env = <ENV_NAME>,
               variant = <APP_VARIANT_NAME>
             )
             .useSite(DatadogSite.US1_FED)
             .build()
         Datadog.initialize(this, configuration, trackingConsent)
     }
 }
```

{% /tab %}

{% tab title="Java" %}

```java
public class SampleApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         Configuration configuration =
                 new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                         .useSite(DatadogSite.US1_FED)
                         .build();
         Datadog.initialize(this, configuration, trackingConsent);
     }
 }
```

{% /tab %}


{% /callout %}

{% callout %}
# Important note for users on the following Datadog sites: ap1.datadoghq.com



{% tab title="Kotlin" %}

```kotlin
class SampleApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         val configuration = Configuration.Builder(
               clientToken = <CLIENT_TOKEN>,
               env = <ENV_NAME>,
               variant = <APP_VARIANT_NAME>
             )
             .useSite(DatadogSite.AP1)
             .build()
         Datadog.initialize(this, configuration, trackingConsent)
     }
 }
```

{% /tab %}

{% tab title="Java" %}

```java
public class SampleApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         Configuration configuration =
                 new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                         .useSite(DatadogSite.AP1)
                         .build();
         Datadog.initialize(this, configuration, trackingConsent);
     }
 }
```

{% /tab %}


{% /callout %}

{% callout %}
# Important note for users on the following Datadog sites: ap2.datadoghq.com



{% tab title="Kotlin" %}

```kotlin
class SampleApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         val configuration = Configuration.Builder(
               clientToken = <CLIENT_TOKEN>,
               env = <ENV_NAME>,
               variant = <APP_VARIANT_NAME>
             )
             .useSite(DatadogSite.AP2)
             .build()
         Datadog.initialize(this, configuration, trackingConsent)
     }
 }
```

{% /tab %}

{% tab title="Java" %}

```java
public class SampleApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         Configuration configuration =
                 new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                         .useSite(DatadogSite.AP2)
                         .build();
         Datadog.initialize(this, configuration, trackingConsent);
     }
 }
```

{% /tab %}


{% /callout %}

To be GDPR compliant, the SDK requires the tracking consent value at initialization. The tracking consent can be one of the following values [see Tracking Consent](https://docs.datadoghq.com/real_user_monitoring/application_monitoring/android/troubleshooting.md#set-tracking-consent-gdpr-compliance):

- `TrackingConsent.PENDING`: The SDK starts collecting and batching the data but does not send it to the data collection endpoint. The SDK waits for the new tracking consent value to decide what to do with the batched data.
- `TrackingConsent.GRANTED`: The SDK starts collecting the data and sends it to the data collection endpoint.
- `TrackingConsent.NOT_GRANTED`: The SDK does not collect any data. You will not be able to manually send any logs, traces, or RUM events.

To update the tracking consent after the SDK is initialized, call: `Datadog.setTrackingConsent(<NEW CONSENT>)`. The SDK changes its behavior according to the new consent. For example, if the current tracking consent is `TrackingConsent.PENDING` and you update it to:

- `TrackingConsent.GRANTED`: The SDK sends all current batched data and future data directly to the data collection endpoint.
- `TrackingConsent.NOT_GRANTED`: The SDK wipes all batched data and does not collect any future data.

Use the utility method `isInitialized` to check if the SDK is properly initialized:

```kotlin
if (Datadog.isInitialized()) {
    // your code here
}
```

When writing your application, you can enable development logs by calling the `setVerbosity` method. All internal messages in the library with a priority equal to or higher than the provided level are then logged to Android's Logcat:

```kotlin
Datadog.setVerbosity(Log.INFO)
```
Configure and enable Trace feature:
{% tab title="Kotlin" %}

```kotlin
val traceConfig = TraceConfiguration.Builder().build()
Trace.enable(traceConfig)
```

{% /tab %}

{% tab title="Java" %}

```java
final TraceConfiguration traceConfig = TraceConfiguration.Builder().build();
Trace.enable(traceConfig);
```

{% /tab %}
Datadog tracer implements the [OpenTelemetry standard](https://opentelemetry.io/docs/concepts/signals/traces/). Create `OtelTracerProvider` and register `OpenTelemetrySdk` in `GlobalOpenTelemetry` in your `onCreate()` method:
{% tab title="Kotlin" %}

```kotlin
GlobalOpenTelemetry.set(object : OpenTelemetry {
    private val tracerProvider = OtelTracerProvider.Builder()
        .setService([BuildConfig.APPLICATION_ID])
        .build()

    override fun getTracerProvider(): TracerProvider {
        return tracerProvider
    }

    override fun getPropagators(): ContextPropagators {
        return ContextPropagators.noop()
    }
})
// and later on if you want to access the tracer
val tracer = GlobalOpenTelemetry.get().getTracer(instrumentationName = "<instrumentation_name>")
```

{% /tab %}

{% tab title="Java" %}

```java
GlobalOpenTelemetry.set(new OpenTelemetry() {
    private final TracerProvider tracerProvider = new OtelTracerProvider.Builder()
            .setService(BuildConfig.APPLICATION_ID)
            .build();

    @Override
    public TracerProvider getTracerProvider() {
        return tracerProvider;
    }

    @Override
    public ContextPropagators getPropagators() {
        return ContextPropagators.noop();
    }
});
// and later on if you want to access the tracer
final Tracer tracer = GlobalOpenTelemetry.get().getTracer("<instrumentation_name>");
```

{% /tab %}

**Note**: Ensure `GlobalOpenTelemetry.set` API is only called once per process. Otherwise, you can create a `TracerProvider` and use it as a singleton in your project.

**Note**: The `setService` method is used to set the service name for the tracer provider. The service name is used to identify the application in the Datadog UI. You can either use the `GlobalOpenTelemetry` to hold a single instance of the `TracerProvider` or create your own instance and use it in your application code as needed.
Instrument your code with the OpenTelemetry API:
{% tab title="Kotlin" %}

```kotlin
val span = tracer.spanBuilder(spanName = "<span_name>").startSpan()
// do something you want to measure ...
// ... then, when the operation is finished:
span.end()
```

{% /tab %}

{% tab title="Java" %}

```java
final Span span = tracer.spanBuilder("<span_name>").startSpan();
// do something you want to measure ...
// ... then, when the operation is finished:
span.end();
```

{% /tab %}
(Optional) Set child-parent relationship between your spans:
{% tab title="Kotlin" %}

```kotlin
val childSpan = tracer.spanBuilder(spanName = "response decoding")
    .setParent(Context.current().with(parentSpan)) // make it child of parent span
    .startSpan()

// ... do your logic here ...
childSpan.end()
```

{% /tab %}

{% tab title="Java" %}

```java
final Span childSpan = tracer.spanBuilder("<span_name>")
    .setParent(Context.current().with(parentSpan)) // make it child of parent span
    .startSpan();

// ... do your logic here ...
childSpan.end();
```

{% /tab %}
(Optional) Provide additional attributes alongside your span:
{% tab title="Kotlin" %}

```kotlin
tracer.spanBuilder(spanName = "<span_name>").setAttribute(key = "<key_name>", value = <key_value>).startSpan()
```

{% /tab %}

{% tab title="Java" %}

```java
tracer.spanBuilder("<span_name>").setAttribute("<key_name>", <key_value>).startSpan();
```

{% /tab %}
(Optional) Attach an error to a span:
{% tab title="Kotlin" %}

```kotlin
span.setStatus(StatusCode.ERROR, description = "<error_description>")

// or if you want to set an exception

span.recordException(exception)
```

{% /tab %}

{% tab title="Java" %}

```java
span.setStatus(StatusCode.ERROR, "<error_description>");

// or if you want to set an exception

span.recordException(exception)
```

{% /tab %}
(Optional) Add span links to your span:
{% tab title="Kotlin" %}

```swift
val linkedSpan = tracer.spanBuilder(spanName = "linked span").startSpan()
linkedSpan.end()

val spanWithLinks = tracer.spanBuilder(spanName = "span with links")
    .addLink(spanContext = linkedSpan.spanContext)
    .startSpan()
spanWithLinks.end()
```

{% /tab %}

{% tab title="Java" %}

```Java
final Span linkedSpan = tracer.spanBuilder("linked span").startSpan();
linkedSpan.end();

final Span spanWithLinks = tracer.spanBuilder("span with links")
        .addLink(linkedSpan.getSpanContext())
        .startSpan();
spanWithLinks.end();
```

{% /tab %}
(Optional) Add local parent span to the span generated around the OkHttp request in RUM:
First, you need to add the OpenTelemetry OkHttp extension module to your project dependencies:

```groovy
android {
    //(...)
}
dependencies {
    implementation "com.datadoghq:dd-sdk-android-okhttp:x.x.x"
    implementation "com.datadoghq:dd-sdk-android-okhttp-otel:x.x.x"
    //(...)
}
```

After you create an `OkHttp` `Request`, you can attach a parent span to the request:

{% tab title="Kotlin" %}

```swift
val parentSpan = tracer.spanBuilder(spanName = "parent span").startSpan()
parentSpan.end()
val request = Request.Builder()
    .url("<URL>")
    .addParentSpan(parentSpan)
    .build()
```

{% /tab %}

{% tab title="Java" %}

```Java
final Span parentSpan = tracer.spanBuilder("parent span").startSpan();
parentSpan.end();
final Request request = new Request.Builder()
    .url("<URL>")
    .addParentSpan(parentSpan)
    .build();
```

{% /tab %}
(Optional) RxJava
To provide a continuous trace inside a RxJava stream you need to follow the steps below:

1. Add the [OpenTelemetry for RxJava](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/rxjava) dependency into your project and follow the **Readme** file for instructions. For example, for a continuous trace you would add:
   ```kotlin
   TracingAssembly.enable()
   ```
1. Then, in your project, open a scope when the Observable is subscribed and close it when it completes. Any span created inside the stream operators is displayed inside this scope (parent Span):

{% tab title="Kotlin" %}

```kotlin
var spanScope: Scope? = null
Single.fromSupplier { }
  .subscribeOn(Schedulers.io())
  .map {
    val span = GlobalOpenTelemetry.get().getTracer("<TRACER_NAME>")
      .spanBuilder("<YOUR_OP_NAME>")
      .startSpan()
    // ...
    span.end()
  }
  .doOnSubscribe {
    val span = GlobalOpenTelemetry.get().getTracer("<TRACER_NAME>")
      .spanBuilder("<YOUR_OP_NAME>")
      .startSpan()
    spanScope = span.makeCurrent()
  }
  .doFinally {
    Span.current()?.end()
    spanScope?.close()
  }
```

{% /tab %}

{% tab title="Java" %}

```java
ThreadLocal<Scope> scopeStorage = new ThreadLocal<>();
// ...
Single.fromSupplier({})
  .subscribeOn(Schedulers.io())
  .map(data -> {
        final Span span = GlobalOpenTelemetry.get().getTracer("<TRACER_NAME>")
            .spanBuilder("<YOUR_OP_NAME>")
            .startSpan();
        // ...
        span.end();
        // ...
  })
  .doOnSubscribe(disposable -> {
        final Span span = GlobalOpenTelemetry.get().getTracer("<TRACER_NAME>")
            .spanBuilder("<YOUR_OP_NAME>")
            .startSpan();
        Scope spanScope = span.makeCurrent();
        scopeStorage.set(spanScope);
  })
  .doFinally(() -> {
        final Span activeSpan = Span.current();
        if (activeSpan != null) {
            activeSpan.end();
        }
        Scope spanScope = scopeStorage.get();
        if (spanScope != null) {
            spanScope.close();
            scopeStorage.remove();
        }
  });
```

{% /tab %}
(Optional) RxJava + Retrofit For a continuous trace inside a RxJava stream that uses Retrofit for the network requests:
- Configure the Datadog Interceptor
- Use the [Retrofit RxJava](https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava3) adapters to use synchronous Observables for the network requests:

{% tab title="Kotlin" %}

```kotlin
Retrofit.Builder()
    .baseUrl("<YOUR_URL>")
    .addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous())
    .client(okHttpClient)
    .build()
```

{% /tab %}

{% tab title="Java" %}

```java
new Retrofit.Builder()
    .baseUrl("<YOUR_URL>")
    .addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous())
    .client(okHttpClient)
    .build();
```

{% /tab %}

- Open a scope around your Rx stream as follows:

{% tab title="Kotlin" %}

```kotlin
var spanScope: Scope? = null
remoteDataSource.getData(query)
    .subscribeOn(Schedulers.io())
    .map {
        // ...
    }
    .doOnSuccess {
        localDataSource.persistData(it)
    }
    .doOnSubscribe {
        val span = GlobalOpenTelemetry.get().getTracer("...")
          .spanBuilder("<YOUR_OP_NAME>")
          .startSpan()
        spanScope = span.makeCurrent()
    }
    .doFinally {
        Span.current()?.end()
        spanScope?.close()
    }
```

{% /tab %}

{% tab title="Java" %}

```java
ThreadLocal<Scope> scopeStorage = new ThreadLocal<>();
// ...
remoteDataSource.getData(query)
    .subscribeOn(Schedulers.io())
    .map(data -> {
        // ...
    })
    .doOnSuccess(data -> {
        localDataSource.persistData(data);
    })
    .doOnSubscribe(disposable -> {
         final Span span = GlobalOpenTelemetry.get().getTracer("...")
           .spanBuilder("<YOUR_OP_NAME>")
           .startSpan();
         Scope spanScope = span.makeCurrent();
         scopeStorage.set(spanScope);
    })
    .doFinally(() -> {
        final Span activeSpan = Span.current();
        if (activeSpan != null) {
            activeSpan.end();
        }
        Scope spanScope = scopeStorage.get();
        if (spanScope != null) {
            spanScope.close();
            scopeStorage.remove();
        }
    });
```

{% /tab %}

## Further reading{% #further-reading %}

- [Explore your services, resources, and traces](https://docs.datadoghq.com/tracing/glossary.md)
- [Interoperability of OpenTelemetry API and Datadog instrumented traces](https://docs.datadoghq.com/opentelemetry/guide/otel_api_tracing_interoperability.md)
