- 重要な情報
- はじめに
- 用語集
- ガイド
- エージェント
- インテグレーション
- OpenTelemetry
- 開発者
- API
- CoScreen
- アプリ内
- Service Management
- インフラストラクチャー
- アプリケーションパフォーマンス
- 継続的インテグレーション
- ログ管理
- セキュリティ
- UX モニタリング
- 管理
Datadog の dd-sdk-android
クライアント側トレーシングライブラリを使用すると、Android アプリケーションから Datadog へトレースを送信すると共に、次の機能を利用できます。
context
およびカスタム属性を追加する。build.gradle
ファイルでライブラリを依存関係として宣言し、Gradle 依存関係を追加します。dependencies {
implementation "com.datadoghq:dd-sdk-android:x.x.x"
}
dd-sdk-android
ライブラリを構成することはできません。クライアントトークンの設定に関する詳細は、クライアントトークンに関するドキュメントを参照してください。US
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
val configuration = Configuration.Builder(
tracesEnabled = true
)
.build()
val credentials = Credentials(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>, <APPLICATION_ID>)
Datadog.initialize(this, credentials, configuration, trackingConsent)
}
}
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Configuration configuration = new Configuration.Builder(true, true, true, true).build();
Credentials credentials = new Credentials(<CLIENT_TOKEN>, <ENV_NAME >, <APP_VARIANT_NAME>, <APPLICATION_ID>);
Datadog.initialize(this, credentials, configuration, trackingConsent);
}
}
EU
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
val configuration = Configuration.Builder(tracesEnabled = true)
.useSite(DatadogSite.EU1)
.build()
val credentials = Credentials(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>, <APPLICATION_ID>)
Datadog.initialize(this, credentials, configuration, trackingConsent)
}
}
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Configuration configuration = new Configuration.Builder(true, true, true, true)
.useSite(DatadogSite.EU1)
.build();
Credentials credentials = new Credentials(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME >, <APPLICATION_ID>);
Datadog.initialize(this, credentials, configuration, trackingConsent);
}
}
GDPR 規定を遵守するため、SDK は初期化時に追跡に関する同意を求めます。 追跡の同意は以下のいずれかの値になります。
TrackingConsent.PENDING
: SDK はデータの収集とバッチ処理を開始しますが、データ
収集エンドポイントへの送信は行われません。SDK はバッチ処理が完了したデータをどうするかについての新たな同意値が得られるまで待機します。TrackingConsent.GRANTED
: SDK はデータの収集を開始し、それをデータ収集エンドポイントに送信します。TrackingConsent.NOT_GRANTED
: SDK がデータを収集することはありません。手動でログやトレース、
RUM イベントを送信することもできません。SDK の初期化後に追跡に関する同意を更新する場合は、 Datadog.setTrackingConsent(<NEW CONSENT>)
を呼び出してください。
SDK は新しい同意に応じて動作を変更します。たとえば、現在の同意内容が TrackingConsent.PENDING
で、それを
TrackingConsent.GRANTED
に更新した場合: SDK は現在のバッチデータと将来的なデータをすべてデータ収集エンドポイントに直接送信します。TrackingConsent.NOT_GRANTED
: SDK はすべてのバッチデータを消去し、以後のデータも収集しません。注: 初期化に必要な認証情報では、アプリケーションのバリアント名も必要となり、値 BuildConfig.FLAVOR
(バリアントがない場合は空白の文字列) の使用が求められることにご注意ください。これは適切な ProGuard mapping.txt
ファイルを有効化し、ビルド時の自動アップロードを行うために重要です。この操作により、難読化を解除された RUM エラースタックトレースを表示できるようになります。詳しくは、Android ソースマッピングファイルのアップロードガイドをご参照ください。
ユーティリティメソッド isInitialized
を使用して SDK が適切に初期化されていることを確認します。
if (Datadog.isInitialized()) {
// your code here
}
アプリケーションを書く際、 setVerbosity
メソッドを呼び出すことで開発ログを有効にできます。指定したレベル以上の優先度を持つライブラリ内のすべての内部メッセージが Android の Logcat に記録されます。
Datadog.setVerbosity(Log.INFO)
onCreate()
メソッドで、一度だけ実行する必要があります。val tracer = AndroidTracer.Builder().build()
GlobalTracer.registerIfAbsent(tracer)
final AndroidTracer tracer = new AndroidTracer.Builder().build();
GlobalTracer.registerIfAbsent(tracer);
1
の場合、各スパンが終了するとすぐに書き込まれます。val tracer = AndroidTracer.Builder()
.setPartialFlushThreshold(10)
.build()
final AndroidTracer tracer = new AndroidTracer.Builder()
.setPartialFlushThreshold(10)
.build();
val tracer = GlobalTracer.get()
val span = tracer.buildSpan("<SPAN_NAME>").start()
// 操作を実行 ...
// ...
// 続いて、スパンを閉じるべき時に
span.finish()
final GlobalTracer tracer = GlobalTracer.get();
final Span span = tracer.buildSpan("<SPAN_NAME>").start();
// 操作を実行 ...
// ...
// 続いて、スパンを閉じるべき時に
span.finish();
val span = tracer.buildSpan("<SPAN_NAME1>").start()
try {
val scope = tracer.activateSpan(span)
scope.use {
// 操作を実行 ...
// ...
// 新しいスコープを開始
val childSpan = tracer.buildSpan("<SPAN_NAME2>").start()
try {
tracer.activateSpan(childSpan).use {
// 操作を実行 ...
}
} catch(e: Error) {
childSpan.error(e)
} finally {
childSpan.finish()
}
}
} catch(e: Error) {
AndroidTracer.logThrowable(span, e)
} finally {
span.finish()
}
final Span = tracer.buildSpan("<SPAN_NAME1>").start();
try {
final Scope scope = tracer.activateSpan(span);
try {
// 操作を実行 ...
// ...
// 新しいスコープを開始
final Span childSpan = tracer.buildSpan("<SPAN_NAME2>").start();
try {
final Scope innerScope = tracer.activateSpan(childSpan);
try {
// 操作を実行 ...
} finally {
innerScope.close();
}
} catch(Error e) {
AndroidTracer.logThrowable(childSpan, e);
} finally {
childSpan.finish();
}
}
finally {
scope.close();
}
} catch(Error e) {
AndroidTracer.logThrowable(span, e);
} finally {
span.finish();
}
非同期呼び出しでスコープを使用する:
val span = tracer.buildSpan("<SPAN_NAME1>").start()
try{
val scope = tracer.activateSpan(span)
scope.use {
// Do something ...
doAsyncWork {
// Step 2: reactivate the Span in the worker thread
val scopeContinuation = tracer.scopeManager().activate(span)
scopeContinuation.use {
// Do something ...
}
}
}
} catch(e: Error) {
AndroidTracer.logThrowable(span, e)
} finally {
span.finish()
}
final Span span = tracer.buildSpan("<SPAN_NAME1>").start();
try {
final Scope scope = tracer.activateSpan(span);
try {
// Do something ...
new Thread(() -> {
// Step 2: reactivate the Span in the worker thread
final Scope scopeContinuation = tracer.scopeManager().activate(span);
try {
// Do something
} finally {
scope.close();
}
}).start();
} finally {
scope.close();
}
} catch (Exception e){
AndroidTracer.logThrowable(span, e);
} finally {
span.finish();
}
(任意) フロントエンド - バックエンドなど、環境間でトレースを手動で分散する:
a. クライアントリクエストにトレーサーコンテキストを挿入します。
val tracer = GlobalTracer.get()
val span = tracer.buildSpan("<SPAN_NAME>").start()
val tracedRequestBuilder = Request.Builder()
tracer.inject(span.context(), Format.Builtin.TEXT_MAP_INJECT,
TextMapInject { key, value ->
tracedRequestBuilder.addHeader(key, value)
}
)
val request = tracedRequestBuilder.build()
// リクエストをディスパッチして、スパンを終了させます。
final Tracer tracer = GlobalTracer.get();
final Span span = tracer.buildSpan("<SPAN_NAME>").start();
final Request.Builder tracedRequestBuilder = new Request.Builder();
tracer.inject(
span.context(),
Format.Builtin.TEXT_MAP_INJECT,
new TextMapInject() {
@Override
public void put(String key, String value) {
tracedRequestBuilder.addHeader(key, value);
}
});
final Request request = tracedRequestBuilder.build();
// リクエストをディスパッチして、スパンを終了させます
b. サーバーコードのヘッダーからクライアントトレーサーコンテキストを抽出します。
val tracer = GlobalTracer.get()
val extractedContext = tracer.extract(
Format.Builtin.TEXT_MAP_EXTRACT,
TextMapExtract {
request.headers().toMultimap()
.map { it.key to it.value.joinToString(";") }
.toMap()
.entrySet()
.iterator()
}
)
val serverSpan = tracer.buildSpan("<SERVER_SPAN_NAME>").asChildOf(extractedContext).start()
final Tracer tracer = GlobalTracer.get();
final SpanContext extractedContext = tracer.extract(
Format.Builtin.TEXT_MAP_EXTRACT,
new TextMapExtract() {
@Override
public Iterator<Map.Entry<String, String>> iterator() {
return request.headers().toMultimap()
.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
entry -> String.join(";", entry.getValue())
)
)
.entrySet()
.iterator();
}
});
final Span serverSpan = tracer.buildSpan("<SERVER_SPAN_NAME>").asChildOf(extractedContext).start();
注: OkHttp クライアントを使用するコードベースの場合、Datadog は以下の実装を提供します。
span.setTag("http.url", url)
span.log(mapOf(Fields.ERROR_OBJECT to throwable))
span.log(mapOf(Fields.MESSAGE to errorMessage))
AndroidTracer で次のヘルパーメソッドのいずれかを使用することもできます。
AndroidTracer.logThrowable(span, throwable)
AndroidTracer.logErrorMessage(span, message)
SpanEventMapper
を実装することで上記の処理を行えます。val config = Configuration.Builder(tracesEnabled = true, ...)
// ...
.setSpanEventMapper(spanEventMapper)
.build()
final Configuration config = new Configuration.Builder(true, true, true, true)
// ...
.setSpanEventMapper(spanEventMapper)
.build();
手動トレースに加えて、dd-sdk-android
ライブラリは次のインテグレーションを提供しています。
OkHttp リクエストをトレースする場合は、次のようにして提供されたインターセプターを追加できます。
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(
DatadogInterceptor(listOf("example.com", "example.eu"), traceSamplingRate = 20f)
)
.build()
float traceSamplingRate = 20f;
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(
new DatadogInterceptor(Arrays.asList("example.com", "example.eu"), traceSamplingRate)
)
.build();
これにより、OkHttpClient によって処理される各リクエストに関するスパンが作成され (提供されたホストに一致)、関連するすべての情報 (URL、メソッド、ステータスコード、エラー) が自動的に入力され、トレース情報がバックエンドに伝播されて、Datadog 内で統合されたトレースが取得されます
ネットワークトレースは、調整可能なサンプリングレートでサンプリングされます。デフォルトでは、20% のサンプリングが適用されます。
インターセプターは、アプリケーションレベルでリクエストを追跡します。ネットワークレベルで TracingInterceptor
を追加すると、さらに詳しいデータを取得(リダイレクトをフォローするなど)できます。
val tracedHosts = listOf("example.com", "example.eu")
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(DatadogInterceptor(tracedHosts, traceSamplingRate = 20f))
.addNetworkInterceptor(TracingInterceptor(tracedHosts, traceSamplingRate = 20f))
.build()
float traceSamplingRate = 20f;
final List<String> tracedHosts = Arrays.asList("example.com", "example.eu");
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new DatadogInterceptor(tracedHosts, traceSamplingRate))
.addNetworkInterceptor(new TracingInterceptor(tracedHosts, traceSamplingRate))
.build();
この場合、特定のリクエストに対して上流のインターセプターが行ったトレースサンプリングの判断は、下流のインターセプターによって尊重されます。
OkHttp リクエストの実行方法 (スレッドプールを使用) のため、リクエストスパンはリクエストをトリガーしたスパンに自動的にリンクされません。次のように、OkHttp Request.Builder
で親スパンを手動で指定することは可能です。
val request = Request.Builder()
.url(requestUrl)
.tag(Span::class.java, parentSpan)
.build()
final Request request = new Request.Builder()
.url(requestUrl)
.tag(Span.class, parentSpan)
.build();
または、dd-sdk-android-ktx
ライブラリで提供される拡張機能を使用している場合
val request = Request.Builder()
.url(requestUrl)
.parentSpan(parentSpan)
.build()
final Request request = new Request.Builder()
.url(requestUrl)
.parentSpan(parentSpan)
.build();
注: 複数のインターセプターを使用する場合、これを最初に呼び出す必要があります。
RxJava ストリーム内で継続的にトレースを提供するには、以下のステップに従う必要があります。
TracingRxJava3Utils.enableTracing(GlobalTracer.get())
var spanScope: Scope? = null
Single.fromSupplier{}
.subscribeOn(Schedulers.io())
.map {
val span = GlobalTracer.get().buildSpan("<YOUR_OP_NAME>").start()
// ...
span.finish()
}
.doOnSubscribe {
val span = GlobalTracer.get()
.buildSpan("<YOUR_OP_NAME>")
.start()
spanScope = GlobalTracer.get().scopeManager().activate(span)
}
.doFinally {
GlobalTracer.get().scopeManager().activeSpan()?.let {
it.finish()
}
spanScope?.close()
}
ThreadLocal<Scope> scopeStorage = new ThreadLocal<>();
...
Single.fromSupplier({})
.subscribeOn(Schedulers.io())
.map(data -> {
final Span span = GlobalTracer.get().buildSpan("<YOUR_OP_NAME>").start();
// ...
span.finish();
// ...
})
.doOnSubscribe(disposable -> {
final Span span = GlobalTracer.get().buildSpan("<YOUR_OP_NAME>").start();
Scope spanScope = GlobalTracer.get().scopeManager().activate(span);
scopeStorage.set(spanScope);
})
.doFinally(() -> {
final Span activeSpan = GlobalTracer.get().scopeManager().activeSpan();
if (activeSpan != null) {
activeSpan.finish();
}
Scope spanScope = scopeStorage.get();
if (spanScope != null) {
spanScope.close();
scopeStorage.remove();
}
})
};
ネットワークリクエストに Retrofit を使用する RxJava ストリーム内の継続的トレースの場合:
Retrofit.Builder()
.baseUrl("<YOUR_URL>")
.addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous())
.client(okHttpClient)
.build()
new Retrofit.Builder()
.baseUrl("<YOUR_URL>")
.addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous())
.client(okHttpClient)
.build();
var spanScope: Scope? = null
remoteDataSource.getData(query)
.subscribeOn(Schedulers.io())
.map { // ... }
.doOnSuccess {
localDataSource.persistData(it)
}
.doOnSubscribe {
val span = GlobalTracer.get().buildSpan("<YOUR_OP_NAME>").start()
spanScope = GlobalTracer.get().scopeManager().activate(span)
}
.doFinally {
GlobalTracer.get().scopeManager().activeSpan()?.let {
it.finish()
}
spanScope?.close()
}
ThreadLocal<Scope> scopeStorage = new ThreadLocal<>();
...
remoteDataSource.getData(query)
.subscribeOn(Schedulers.io())
.map(data -> { // ... })
.doOnSuccess(data -> {
localDataSource.persistData(data);
})
.doOnSubscribe(disposable -> {
final Span span = GlobalTracer.get().buildSpan("<YOUR_OP_NAME>").start();
Scope spanScope = GlobalTracer.get().scopeManager().activate(span);
scopeStorage.set(spanScope);
})
.doFinally(() -> {
final Span activeSpan = GlobalTracer.get().scopeManager().activeSpan();
if (activeSpan != null) {
activeSpan.finish();
}
Scope spanScope = scopeStorage.get();
if (spanScope != null) {
spanScope.close();
scopeStorage.remove();
}
});
すべてのスパンは、最初にローカルデバイスにバッチで格納されます。各バッチはインテークの仕様に従います。ネットワークが利用可能で、Datadog SDK がエンドユーザーのエクスペリエンスに影響を与えないようにバッテリーの残量が十分にあれば、バッチはすぐに送信されます。アプリケーションがフォアグラウンドにあるときにネットワークが利用できない場合、またはデータのアップロードが失敗した場合、バッチは正常に送信されるまで保持されます。
つまり、ユーザーがオフラインでアプリケーションを開いても、データが失われることはありません。
ディスク上のデータは、古すぎる場合は SDK がディスク容量を使いすぎないようにするために自動的に破棄されます。
お役に立つドキュメント、リンクや記事: