Android and Android TV Custom Instrumentation using the OpenTelemetry API

이 페이지는 아직 한국어로 제공되지 않으며 번역 작업 중입니다. 번역에 관한 질문이나 의견이 있으시면 언제든지 저희에게 연락해 주십시오.
Datadog과 함께 OpenTelemetry를 언제 사용해야 하는지 OpenTelemetry API를 사용한 커스텀 계측에서 확인해 보세요.

개요

다음의 경우 OpenTelemetry API를 사용해 애플리케이션을 수동으로 계측해야 합니다.

  • Datadog이 지원하는 라이브러리 계측을 사용하고 있지 않습니다.
  • ddtrace 라이브러리의 기능을 확장하고 싶습니다.
  • 애플리케이션 계측을 보다 세밀하게 제어해야 합니다.

ddtrace 라이브러리는 이러한 목표를 달성하는 데 도움이 됩니다. 다음 섹션에서는 커스텀 계측을 위해 Datadog과 OpenTelemetry API를 함께 사용하는 방법을 다룹니다.

Requirements and limitations

Setup

  1. 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]"
        // ...
    }
}
  1. 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)
  1. Configure and enable Trace feature:
val traceConfig = TraceConfiguration.Builder().build()
Trace.enable(traceConfig)
final TraceConfiguration traceConfig = TraceConfiguration.Builder().build();
Trace.enable(traceConfig);
  1. 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.

  1. 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();
  1. (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();
  1. (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();
  1. (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)
  1. (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();
  1. (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