本文介绍通过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以下的设备。