本文介紹通過OpenTelemetry Java SDK將Android應用的Trace數據接入到日志服務的操作步驟。
前提條件
已創建Trace實例。更多信息,請參見創建Trace實例。
步驟一:SDK集成
在App或Module級別的build.gradle文件中添加如下配置。
// BOM清單,用于同步依賴版本。
implementation(platform("io.opentelemetry:opentelemetry-bom:1.22.0"))
// API接口
implementation("io.opentelemetry:opentelemetry-api")
implementation("io.opentelemetry:opentelemetry-context")
// API extensions
implementation("io.opentelemetry:opentelemetry-extension-kotlin")
// SDK
implementation('io.opentelemetry:opentelemetry-sdk')
implementation('io.opentelemetry:opentelemetry-sdk-logs')
// semantic conventions
implementation("io.opentelemetry:opentelemetry-semconv")
// Exporters
// 官方gRPC Exporter
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
更多信息,請參見OpenTelemetry Java SDK Release。
步驟二:權限配置
上報Trace數據時,需申請網絡權限,即需要在AndroidManifest.xml文件中添加如下權限申明。
<uses-permission android:name="android.permission.INTERNET" />
步驟三:初始化SDK
一般建議在Application類的onCreate方法中進行SDK初始化。
// 初始化Exporter。Exporter用于導出Trace數據到日志服務Logstore。
OtlpGrpcSpanExporter grpcSpanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("https://${endpoint}")
.addHeader("x-sls-otel-project", "${project}")
.addHeader("x-sls-otel-instance-id", "${instanceId}")
.addHeader("x-sls-otel-ak-id", "${access-key-id}")
.addHeader("x-sls-otel-ak-secret", "${access-key-secret}")
.build();
// 初始化tracer provider。tracer provider用于暴露主要的API接口,對Span進行預處理、自定義Clock。
// 自定義TraceId、SpanId生成規則,自定義采樣器等。您可以根據實際需求進行配置。
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(grpcSpanExporter).build())
.setResource(Resource.create(Attributes.builder()
.put(ResourceAttributes.SERVICE_NAME, "${service}")
.put(ResourceAttributes.SERVICE_NAMESPACE, "${service.namespace}")
.put(ResourceAttributes.SERVICE_VERSION, "${version}")
.put(ResourceAttributes.HOST_NAME, "${host}")
.put(ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "${environment}")
.build()
)
)
.build();
// 初始化OpenTelemetrySdk。
OpenTelemetrySdk telemetrySdk = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.build(); // 如果通過build() 方法生成實例,則需要根據實際情況判斷是否需要全局持有telemetrySdk。
// 您也可以初始化一個全局的OpenTelemetrySdk。
// OpenTelemetrySdk telemetrySdk = OpenTelemetrySdk.builder()
// .setTracerProvider(tracerProvider)
// .buildAndRegisterGlobal(); // 后續可通過GlobalOpenTelemetry.get()或GlobalOpenTelemetry.getTracer()進行訪問。
變量 | 說明 | 示例 |
| 服務入口是訪問一個Project及其內部數據的URL,日志服務提供私網域名和公網域名。更多信息,請參見服務入口。
| cn-hangzhou.log.aliyuncs.com:10010 |
| 日志服務Project名稱,更多信息,請參見管理Project。 | test-project |
| Trace服務實例ID。更多信息,請參見創建Trace實例。 | test-traces |
| AccessKey ID用于標識用戶,更多信息,請參見訪問密鑰。 建議您遵循最小化原則,按需授予RAM用戶必要的權限。關于授權的具體操作,請參見創建RAM用戶及授權,RAM自定義授權示例。 | 無 |
| AccessKey Secret是用戶用于加密簽名字符串和日志服務用來驗證簽名字符串的密鑰,必須保密。 | 無 |
| 服務歸屬的命名空間。 | order |
| 服務名,根據您的實際場景配置。 | payment |
| 服務版本號,建議按照 | v1.0.0 |
| 主機名。 | localhost |
| 部署環境,例如測試環境、生產環境。 | pre |
步驟四:使用SDK
創建Tracer
建議根據不同的業務場景創建Tracer。創建Tracer時,需傳入instrumentation scope name,利于按照scope區分不同的Trace數據。
Tracer tracer = telemetrySdk.getTracer("otel application", "1.0.0");
創建基本Span
Span代表了事務中的操作。每個Span都封裝了操作名稱、起止時間戳、屬性信息、事件信息和Context信息等。
final Span span = tracer.spanBuilder("root span").startSpan();
// do stuff
// ...
span.end();
創建嵌套Span
當您希望為嵌套操作關聯Span時,OpenTelemetry支持在進程內和跨遠程進程進行跟蹤。例如針對methodA調用methodB ,您可以通過以下方式創建嵌套Span。
void methodA() {
Span parentSpan = tracer.spanBuilder("operation A").startSpan();
methodB(parentSpan);
parentSpan.end();
}
void methodB(Span parentSpan) {
Span childSpan = tracer.spanBuilder("operation B")
.setParent(parentSpan)
.startSpan();
// do stuff
childSpan.end();
}
OpenTemetry API還提供了一種自動化的方式來傳播parentSpan。
void methodA() {
Span parentSpan = tracer.spanBuilder("operation A").startSpan();
try (Scope scope = parentSpan.makeCurrent()) {
methodB();
} finally {
parentSpan.end();
}
}
void methodB() {
Span childSpan = tracer.spanBuilder("operation B").startSpan();
// do stuff
childSpan.end();
}
創建帶屬性的Span
您可以通過屬性在Span上提供特定操作的上下文信息。例如執行結果、關聯的其他業務信息等。
Span span = tracer.spanBuilder("GET /resource/catalog").setSpanKind(Span.Kind.CLIENT).startSpan();
span.setAttribute("http.method", "GET");
span.setAttribute("http.url", url.toString());
創建攜帶事件的Span
您可以通過攜帶多個事件的方式對Span進行注釋。
span.addEvent("start");
// do stuff
// ...
span.addEvent("end");
// 也可以攜帶屬性。
Attributes eventAttributes = Attributes.of(
"key1", AttributeValue.stringAttributeValue("value1"),
"key2", AttributeValue.longAttributeValue(111L)
);
span.addEvent("End Computation", eventAttributes);
創建帶鏈接的Span
一個Span可以鏈接一個或多個因果相關的其他Span。
Link link1 = LinkData.create(parentSpan1.getContext());
Link link2 = LinkData.create(parentSpan2.getContext());
Span child = tracer.spanBuilder("child_with_link")
.addLink(link1)
.addLink(link2)
.addLink(parentSpan3.getContext())
.startSpan();
從遠程進程中讀取上下文信息的具體操作,請參見Context Propagation 。
給Span添加狀態
Span包含StatusCode.UNSET
、StatusCode.OK
、StatusCode.ERROR
三個狀態,分別表示默認狀態、成功狀態、包含錯誤。
Span span = tracer.spanBuilder("operation name").startSpan();
try (Scope scope = span.makeCurrent()) {
// do stuff
} catch (Throwable t) {
span.setStatus(StatusCode.ERROR, "Something bad happened!");
throw t;
} finally {
span.end(); // 調用end()方法調用后,無法設置狀態信息。
}
給Span添加異常信息
建議在捕獲到異常信息之后,把異常信息添加到關聯的Span中。同時,也建議同步更新Span狀態。
Span span = tracer.spanBuilder("operation name").startSpan();
try (Scope scope = span.makeCurrent()) {
// do stuff
} catch (Throwable throwable) {
span.setStatus(StatusCode.ERROR, "Something bad happened!");
span.recordException(throwable)
} finally {
span.end();
}
傳播上下文信息
OpenTelemetry提供了一種基于文本的方法,傳播上下文信息。以下是使用HttpURLConnection
發出HTTP請求的示例。
// 基于HttpURLConnection Header setter
TextMapSetter<HttpURLConnection> setter =
new TextMapSetter<HttpURLConnection>() {
@Override
public void set(HttpURLConnection carrier, String key, String value) {
// Insert the context as Header
carrier.setRequestProperty(key, value);
}
};
URL url = new URL("http://127.0.0.1:8088/resource/catalog");
Span httpSpan = tracer.spanBuilder("GET /resource/catalog").setSpanKind(SpanKind.CLIENT).startSpan();
try (Scope scope = httpSpan.makeCurrent()) {
// 注入屬性信息,記錄HTTP請求的相關詳情。
httpSpan.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
httpSpan.setAttribute(SemanticAttributes.HTTP_URL, url.toString());
HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();
// 把當前的Context信息注入到HTTP請求中。
telemetrySdk.getPropagators().getTextMapPropagator().inject(Context.current(), transportLayer, setter);
// do stuff
} finally {
httpSpan.end();
}
目前,OpenTelemetry SDK支持按照W3C Trace Context標準傳播上下文信息。更多信息,請參見W3CTraceContextPropagator類。
關于OpenTelemetry SDK的更多信息,請參考官方文檔。
常見問題
OpenTelemetry Java SDK使用了Java 8+ API特性后,你的設備上可能會出現如下錯誤信息。
FATAL EXCEPTION: main
Process: xx.xx.xx.xx, PID: 2810
java.lang.NoClassDefFoundError: io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder$$ExternalSyntheticLambda0
at io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder.<init>(OtlpGrpcSpanExporterBuilder.java:38)
at io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter.builder(OtlpGrpcSpanExporter.java:39)
針對上述問題,如果你的Android應用支持API 26以下的設備,則您需要打開coreLibraryDesugaringEnabled
開關。具體操作,請參見 core library desugaring。
打開coreLibraryDesugaringEnabled
開關后,你的Android應用最低可以支持到API 21的設備。
目前OpenTelemetry Java SDK不支持API 21以下的設備。