- 필수 기능
- 시작하기
- Glossary
- 표준 속성
- Guides
- Agent
- 통합
- 개방형텔레메트리
- 개발자
- Administrator's Guide
- API
- Datadog Mobile App
- CoScreen
- Cloudcraft
- 앱 내
- 서비스 관리
- 인프라스트럭처
- 애플리케이션 성능
- APM
- Continuous Profiler
- 스팬 시각화
- 데이터 스트림 모니터링
- 데이터 작업 모니터링
- 디지털 경험
- 소프트웨어 제공
- 보안
- AI Observability
- 로그 관리
- 관리
",t};e.buildCustomizationMenuUi=t;function n(e){let t='
",t}function s(e){let n=e.filter.currentValue||e.filter.defaultValue,t='${e.filter.label}
`,e.filter.options.forEach(s=>{let o=s.id===n;t+=``}),t+="${e.filter.label}
`,t+=`Send traces to Datadog from your Android applications with Datadog’s
dd-sdk-android-trace
client-side tracing library and leverage the following features:
context
and extra custom attributes to each span sent.다음의 경우 OpenTelemetry API를 사용해 애플리케이션을 수동으로 계측해야 합니다.
ddtrace
라이브러리의 기능을 확장하고 싶습니다.ddtrace
라이브러리는 이러한 목표를 달성하는 데 도움이 됩니다. 다음 섹션에서는 커스텀 계측을 위해 Datadog과 OpenTelemetry API를 함께 사용하는 방법을 다룹니다.
The Datadog Tracer implements the OpenTelemetry standard, and Datadog recommends using it as an interface for tracing your application because it’s vendor-neutral, supports many languages and frameworks, and unifies traces, metrics, and logs under one standard. See instructions on setting up OpenTelemetry integration with the SDK.
Note: The OpenTelemetry specification library requires desugaring to be enabled for projects with a minSdk
< 26
. If you cannot enable desugaring in your project, you can still use the Trace product with the Datadog API instead.
Note: The Datadog API implementation helps you transition from OpenTracing to OpenTelemetry.
build.gradle
file:dependencies {
implementation "com.datadoghq:dd-sdk-android-trace:x.x.x"
}
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);
}
}
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)
}
}
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);
}
}
To be compliant with the GDPR regulation, the SDK requires the tracking consent value at initialization. The tracking consent can be one of the following values:
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.Note: In the credentials required for initialization, your application variant name is also
required, and should use your BuildConfig.FLAVOR
value (or an empty string if you don’t have
variants). This is important because it enables the right ProGuard mapping.txt
file to be
automatically uploaded at build time to be able to view de-obfuscated RUM error stack traces. For
more information see the guide to uploading Android source mapping files.
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)
val traceConfig = TraceConfiguration.Builder().build()
Trace.enable(traceConfig)
TraceConfiguration traceConfig = TraceConfiguration.Builder().build();
Trace.enable(traceConfig);
DatadogTracer
. You only need to do it once, usually in your
application’s onCreate()
method:import com.datadog.android.trace.GlobalDatadogTracer
import com.datadog.android.trace.DatadogTracing
GlobalDatadogTracer.registerIfAbsent(
DatadogTracing.newTracerBuilder()
.build()
)
import com.datadog.android.trace.GlobalDatadogTracer;
import com.datadog.android.trace.DatadogTracing;
GlobalDatadogTracer.registerIfAbsent(
DatadogTracing.newTracerBuilder(Datadog.getInstance()).build()
);
1
writes each span as soon as
it finishes.val tracer = DatadogTracing.newTracerBuilder()
.withPartialFlushMinSpans(10)
.build()
DatadogTracer tracer = DatadogTracing.newTracerBuilder(Datadog.getInstance())
.withPartialFlushMinSpans(10)
.build();
val tracer = GlobalDatadogTracer.get()
val span = tracer.buildSpan("<SPAN_NAME>").start()
// Do something ...
// ...
// Then when the span should be closed
span.finish()
DatadogTracer tracer = GlobalDatadogTracer.get();
DatadogSpan span = tracer.buildSpan("<SPAN_NAME>").start();
// Do something ...
// ...
// Then when the span should be closed
span.finish();
val span = tracer.buildSpan("<SPAN_NAME1>").start()
try {
val scope = tracer.activateSpan(span)
scope?.use {
// Do something ...
// ...
// Start a new Scope
val childSpan = tracer.buildSpan("<SPAN_NAME2>").start()
try {
val innerScope = tracer.activateSpan(childSpan).use { innerScope ->
// Do something ...
}
} catch (e: Throwable) {
childSpan.logThrowable(e)
} finally {
childSpan.finish()
}
}
} catch (e: Error) {
}
DatadogSpan span = tracer.buildSpan("<SPAN_NAME1>").start();
try {
DatadogScope scope = tracer.activateSpan(span);
try {
// Do something ...
// ...
// Start a new Scope
DatadogSpan childSpan = tracer.buildSpan("<SPAN_NAME2>").start();
try {
DatadogScope innerScope = tracer.activateSpan(childSpan);
try {
// Do something ...
}
finally {
innerScope.close();
}
} catch( Throwable e) {
childSpan.logThrowable(e);
} finally {
childSpan.finish();
}
}
finally {
scope.close();
}
} catch(Error e){
}
val span = tracer.buildSpan("<SPAN_NAME1>").start()
try {
val scope = tracer.activateSpan(span)
scope.use {
// Do something ...
Thread {
// Step 2: reactivate the Span in the worker thread
tracer.activateSpan(span).use {
// Do something ...
}
}.start()
}
} catch (e: Throwable) {
span.logThrowable(e)
} finally {
span.finish()
}
DatadogSpan span = tracer.buildSpan("<SPAN_NAME1>").start();
try {
DatadogScope scope = tracer.activateSpan(span);
try {
// Do something ...
new Thread(() ->{
// Step 2: reactivate the Span in the worker thread
DatadogScope scopeContinuation = tracer.activateSpan(span);
try {
// Do something
} finally {
scope.close();
}
}).start();
} finally {
scope.close();
}
} catch(Throwable e) {
span.logThrowable(e);
} finally {
span.finish();
}
(Optional) To manually distribute traces between your environments, for example frontend to backend:
a. Inject tracer context in the client request.
val tracer = GlobalDatadogTracer.get()
val span = tracer.buildSpan("<SPAN_NAME>").start()
val tracedRequestBuilder = Request.Builder()
tracer.propagate().inject<Request.Builder?>(
span.context(),
tracedRequestBuilder
) { builder, key, value ->
builder?.addHeader(key, value)
}
val request = tracedRequestBuilder.build()
// Dispatch the request and finish the span after.
DatadogTracer tracer = GlobalDatadogTracer.get();
DatadogSpan span = tracer.buildSpan("<SPAN_NAME>").start();
Request.Builder tracedRequestBuilder = new Request.Builder();
tracer.propagate().inject(
span.context(),
tracedRequestBuilder,
new Function3<Request.Builder,String,String,Unit>(){
@Override
public Unit invoke(Request.Builder builder, String key, String value) {
builder.addHeader(key, value);
return Unit.INSTANCE;
}
});
Request request = tracedRequestBuilder.build();
// Dispatch the request and finish the span after.
b. Extract the client tracer context from headers in server code.
val tracer = GlobalDatadogTracer.get()
val extractedContext = tracer.propagate()
.extract(request) { carrier, classifier ->
val headers = carrier.headers.toMultimap()
.map { it.key to it.value.joinToString(";") }
.toMap()
for ((key, value) in headers) classifier(key, value)
}
val serverSpan = tracer.buildSpan("<SERVER_SPAN_NAME>").withParentContext(extractedContext).start()
DatadogTracer tracer = GlobalDatadogTracer.get();
DatadogSpanContext extractedContext = tracer.propagate()
.extract(request,
new Function2<Request, Function2<? super String, ? super String, Boolean>, Unit>() {
@Override
public Unit invoke(
Request carrier,
Function2<? super String, ? super String, Boolean> classifier
) {
request.headers().forEach(pair -> {
String key = pair.component1();
String value = pair.component2();
classifier.invoke(key, value);
});
return Unit.INSTANCE;
}
});
DatadogSpan serverSpan = tracer.buildSpan("<SERVER_SPAN_NAME>").withParentContext(extractedContext).start();
Note: For code bases using the OkHttp client, Datadog provides the implementation below.
span.setTag("http.url", url)
span.logThrowable(throwable)
span.logErrorMessage(message)
SpanEventMapper
when enabling Trace feature:val traceConfig = TraceConfiguration.Builder()
// ...
.setEventMapper(spanEventMapper)
.build()
TraceConfiguration config = new TraceConfiguration.Builder()
// ...
.setEventMapper(spanEventMapper)
.build();
To monitor the performance of a given lambda, you can use the withinSpan()
method. By default, a
scope will be created for the span, but you can disable this behavior by setting the activate
parameter to false.
import com.datadog.android.trace.withinSpan
import com.datadog.android.trace.api.span.DatadogSpan
withinSpan("<SPAN_NAME>", parentSpan, activate) {
// Your code here
}
If you are using SQLiteDatabase
to persist data locally, you can trace the database transaction
using the following method:
import com.datadog.android.trace.sqlite.transactionTraced
import android.database.sqlite.SQLiteDatabase
sqliteDatabase.transactionTraced("<SPAN_NAME>", isExclusive) { database ->
// Your queries here
database.insert("<TABLE_NAME>", null, contentValues)
// Decorate the Span
setTag("<TAG_KEY>", "<TAG_VALUE>")
}
It behaves like the SQLiteDatabase.transaction
method provided in the core-ktx
AndroidX package
and only requires a span operation name.
In addition to manual tracing, the Datadog SDK provides the following integrations.
If you want to trace your OkHttp requests, you can add the provided Interceptor (which can be
found in the dd-sdk-android-okhttp
library) as follows:
dd-sdk-android-okhttp
library in the module-level
build.gradle
file:dependencies {
implementation "com.datadoghq:dd-sdk-android-okhttp:x.x.x"
}
DatadogInterceptor
to your OkHttpClient
:val tracedHosts = listOf("example.com", "example.eu")
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(
DatadogInterceptor.Builder(tracedHosts)
.setTraceSampler(RateBasedSampler(20f))
.build()
)
.build()
List<String> tracedHosts = Arrays.asList("example.com", "example.eu");
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(
new DatadogInterceptor.Builder(tracedHosts)
.setTraceSampler(new RateBasedSampler(20f))
.build()
)
.build();
This creates a span around each request processed by the OkHttpClient (matching the provided hosts), with all the relevant information automatically filled (URL, method, status code, error), and propagates the tracing information to your backend to get a unified trace within Datadog.
Network traces are sampled with an adjustable sampling rate. A sampling of 100% is applied by default.
The interceptor tracks requests at the application level. You can also add a TracingInterceptor
at
the network level to get more details, for example when following redirections.
val tracedHosts = listOf("example.com", "example.eu")
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(
DatadogInterceptor.Builder(tracedHosts)
.setTraceSampler(RateBasedSampler(20f))
.build()
)
.addNetworkInterceptor(
TracingInterceptor.Builder(tracedHosts)
.setTraceSampler(RateBasedSampler(100f))
.build()
)
.build()
List<String> tracedHosts = Arrays.asList("example.com", "example.eu");
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(
new DatadogInterceptor.Builder(tracedHosts)
.setTraceSampler(new RateBasedSampler(20f))
.build()
)
.addNetworkInterceptor(
new TracingInterceptor.Builder(tracedHosts)
.setTraceSampler(new RateBasedSampler(20f))
.build()
)
.build();
In this case trace sampling decision made by the upstream interceptor for a particular request will be respected by the downstream interceptor.
Because the way the OkHttp Request is executed (using a Thread pool), the request span won’t be
automatically linked with the span that triggered the request. You can manually provide a parent
span in the OkHttp Request.Builder
as follows by using Request.Builder.parentSpan
extension
method:
val request = Request.Builder()
.url(requestUrl)
.parentSpan(parentSpan)
.build()
Request.Builder requestBuilder = new Request.Builder()
.url(requestUrl)
Request request = OkHttpRequestExtKt
.parentSpan(requestBuilder, parentSpan)
.build();
Note:
GlobalDatadogTracer
, make sure the same tracing header types are set for the
tracer in use.All the spans are first stored on the local device in batches. Each batch follows the intake specification. They are sent as soon as network is available, and the battery is high enough to ensure the Datadog SDK does not impact the end user’s experience. If the network is not available while your application is in the foreground, or if an upload of data fails, the batch is kept until it can be sent successfully.
This means that even if users open your application while being offline, no data will be lost.
The data on disk will automatically be discarded if it gets too old to ensure the SDK doesn’t use too much disk space.
The following methods in DatadogTracerBuilder
can be used when initializing the DatadogTracer
:
Method | Description |
---|---|
withServiceName(<SERVICE_NAME>) | Set the value for the service . |
withPartialFlushMinSpans(<INT>) | When this threshold is reached (you have a specific <INT> amount of spans closed waiting), the flush mechanism is triggered and all pending closed spans are processed and sent to intake. |
withTag(<KEY>, <VALUE>) | Set a <KEY>:<VALUE> pair of tags to be added to spans created by the Tracer. |
setBundleWithRumEnabled(true) | Set to true to enable spans to be enriched with the current RUM View information. This enables you to see all of the spans produced during a specific View lifespan in the RUM Explorer. |
withSampleRate(<FLOAT>) | Set a value 0-100 to define the percentage of Traces to collect. |
withTracingHeadersTypes(Set<TracingHeaderType>) | Sets the tracing header styles that may be injected by the Tracer. |
setTraceRateLimit(<INT>) | Sets the trace rate limit. This is the maximum number of traces per second that will be accepted. |