Android and Android TV Custom Instrumentation using the OpenTelemetry API
This page is not yet available in Spanish. We are working on its translation.
If you have any questions or feedback about our current translation project,
feel free to reach out to us!Overview
There are a few reasons to manually instrument your applications with the OpenTelemetry API:
- You are not using Datadog supported library instrumentation.
- 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
Setup
- Add Android Trace and Android Trace OpenTelemetry dependencies to your application module’s
build.gradle
file:
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:
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. For security reasons, you must use a client token, not Datadog API keys, to configure Datadog SDK.
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)
}
}
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);
}
}
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)
}
}
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);
}
}
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)
}
}
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);
}
}
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)
}
}
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);
}
}
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)
}
}
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);
}
}
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)
}
}
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);
}
}
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:
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:
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:
Datadog.setVerbosity(Log.INFO)
- Configure and enable Trace feature:
val traceConfig = TraceConfiguration.Builder().build()
Trace.enable(traceConfig)
final TraceConfiguration traceConfig = TraceConfiguration.Builder().build();
Trace.enable(traceConfig);
- Datadog tracer implements the OpenTelemetry standard. Create
OtelTracerProvider
and register OpenTelemetrySdk
in GlobalOpenTelemetry
in your onCreate()
method:
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 provider
val tracerProvider = GlobalOpenTelemetry.get().getTracer(instrumentationName = "<instrumentation_name>")
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 provider
final TracerProvider tracerProvider = GlobalOpenTelemetry.get().getTracer("<instrumentation_name>");
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:
val span = tracer.spanBuilder(spanName = "<span_name>").startSpan()
// do something you want to measure ...
// ... then, when the operation is finished:
span.end()
final Span span = tracer.spanBuilder("<span_name>").startSpan();
// do something you want to measure ...
// ... then, when the operation is finished:
span.end();
- (Optional) Set child-parent relationship between your spans:
let childSpan = tracer.spanBuilder(spanName = "response decoding")
.setParent(Context.current().with(parentSpan)) // make it child of parent span
.startSpan()
// ... do your logic here ...
childSpan.end()
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();
- (Optional) Provide additional attributes alongside your span:
tracer.spanBuilder(spanName = "<span_name>").setAttribute(key = "<key_name>", value = <key_value>).startSpan()
tracer.spanBuilder("<span_name>").setAttribute("<key_name>", <key_value>).startSpan();
- (Optional) Attach an error to a span:
span.setStatus(StatusCode.ERROR, description = "<error_description>")
// or if you want to set an exception
span.recordException(exception)
span.setStatus(StatusCode.ERROR, "<error_description>")
// or if you want to set an exception
span.recordException(exception)
- (Optional) Add span links to your span:
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()
final Span linkedSpan = tracer.spanBuilder("linked span").startSpan();
linkedSpan.end();
final Span spanWithLinks = tracer.spanBuilder("span with links")
.addLink(linkedSpan.getSpanContext())
.startSpan();
spanWithLinks.end();
- (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:
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:
val parentSpan = tracer.spanBuilder(spanName = "parent span").startSpan()
parentSpan.end()
val request = Request.Builder()
.url("<URL>")
.addParentSpan(parentSpan)
.build()
final Span parentSpan = tracer.spanBuilder("parent span").startSpan();
parentSpan.end()
final Request:request = new Request.Builder()
.url("<URL>")
.addParentSpan(parentSpan)
.build();
Further reading
Más enlaces, artículos y documentación útiles: