通过OpenTelemetry为应用埋点并上报链路数据至可观测链路 OpenTelemetry 版后,可观测链路 OpenTelemetry 版即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OpenTelemetry为Android应用埋点并上报数据。
前提条件
示例Demo
本文将通过具体示例介绍如何使用OpenTelemetry上报Android应用程序的链路数据,该方法同样适用于Java语言和Kotlin语言应用。
示例代码仓库地址:opentelemetry-android-demo
步骤一:创建并配置应用
创建应用。
在Android Studio中新建应用,选择Basic Views Activity应用模板,然后单击Next。
选择Java语言或者Kotlin语言,SDK支持的最小版本选择API 24: Android 7.0 (Nougat),然后单击Finish。
添加依赖项。
在Module或Project级别的build.gradle中,添加以下依赖。
本例中使用的OpenTelemetry Java SDK版本为1.25.0,更多版本请参见Opentelemetry Java Releases。build.gradle完整示例代码请参见build.gradle。
implementation platform('io.opentelemetry:opentelemetry-bom:1.25.0') implementation "io.opentelemetry:opentelemetry-api" implementation "io.opentelemetry:opentelemetry-context" implementation 'io.opentelemetry:opentelemetry-exporter-otlp' implementation 'io.opentelemetry:opentelemetry-exporter-logging' implementation 'io.opentelemetry:opentelemetry-extension-kotlin' implementation 'io.opentelemetry:opentelemetry-sdk' implementation 'io.opentelemetry:opentelemetry-semconv'
添加网络配置。
在
app/res/xml
目录下创建network_security_config.xml文件并添加以下内容。<!-- 查看文件完整内容: https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/res/xml/network_security_config.xml --> <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <!-- 请将下面的域名替换为您在前提条件中获取的接入点,请不要包含"http://"、端口号和URL路径 --> <domain includeSubdomains="true">tracing-analysis-dc-hz.aliyuncs.com</domain> </domain-config> </network-security-config>
修改
app/src/main/AndroidManifest.xml
文件,添加以下两行内容为应用开启网络权限。<!-- 查看文件完整内容: https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/AndroidManifest.xml --> <?xml version="1.0" encoding="utf-8"?> <manifest ...> <!-- 添加下面一行内容,以开启网络权限 --> <uses-permission android:name="android.permission.INTERNET" /> <application ... <!-- 添加下面一行内容,对要上报数据的域名进行网络配置 --> android:networkSecurityConfig="@xml/network_security_config" ...> ... </application> </manifest>
步骤二:OpenTelemetry初始化
创建OpenTelemetry工具类。
在MainActivity所在的同级目录下创建OpenTelemetryUtil文件并添加以下内容。
方法一:通过gRPC上报Trace数据
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/java/com/example/androidjavademo/OpenTelemetryUtil.java */ Resource otelResource = Resource.getDefault().merge( Resource.create( Attributes.of( // 请将<your-service-name>替换为您的应用名。 ResourceAttributes.SERVICE_NAME, "<your-service-name>", // 请将<your-host-name>替换为您的主机名。 ResourceAttributes.HOST_NAME, "<your-host-name>" ) ) ); SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) // 可选,将链路数据打印到日志/命令行,如不需要请注释这一行。 // 请将<gRPC-endpoint>替换为从前提条件中获取的接入点,<gRPC-token>替换为鉴权Token。 .addSpanProcessor(BatchSpanProcessor.builder( OtlpGrpcSpanExporter.builder() .setEndpoint("<gRPC-endpoint>") // 例如:http://tracing-analysis-dc-hz.aliyuncs.com:8090 .addHeader("Authentication", "<gRPC-token>") // 例如:xxxx@xxxx_xxxx@xxxx .build()).build() ) .setResource(otelResource) .build(); OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); // 获取tracer,用来创建Span。 tracer = openTelemetry.getTracer("android-tracer", "1.0.0");
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidKotlinDemo/app/src/main/java/com/example/androidkotlindemo/OpenTelemetryUtil.kt */ val otelResource = Resource.getDefault().merge( Resource.create( Attributes.of( ResourceAttributes.SERVICE_NAME, "<your-service-name>", // 请将<your-service-name>替换为您的应用名。 ResourceAttributes.HOST_NAME, "<your-host-name>" // 请将<your-host-name>替换为您的主机名。 ) ) ) /* 使用gRPC协议上报链路数据 */ val sdkTracerProvider = SdkTracerProvider.builder() .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) // 可选,将链路数据打印到日志/命令行,如不需要请注释这一行。 // 请将<gRPC-endpoint>替换为从前提条件中获取的接入点,<gRPC-token>替换为鉴权Token。 .addSpanProcessor( BatchSpanProcessor.builder( OtlpGrpcSpanExporter.builder() .setEndpoint("<gRPC-endpoint>") // 例如:http://tracing-analysis-dc-hz.aliyuncs.com:8090 .addHeader("Authentication", "<gRPC-token>") // 例如:xxxx@xxxx_xxxx@xxxx .build() ).build() ) .setResource(otelResource) .build() val openTelemetry: OpenTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal() // 获取tracer,用来创建Span。 tracer = openTelemetry.getTracer("android-tracer", "1.0.0")
方法二:通过HTTP上报Trace数据
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/java/com/example/androidjavademo/OpenTelemetryUtil.java */ Resource otelResource = Resource.getDefault().merge( Resource.create( Attributes.of( // 请将<your-service-name>替换为您的应用名。 ResourceAttributes.SERVICE_NAME, "<your-service-name>", // 请将<your-host-name>替换为您的主机名。 ResourceAttributes.HOST_NAME, "<your-host-name>" ) ) ); SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) // 可选,将链路数据打印到日志/命令行,如不需要请注释这一行 // 请将<HTTP-endpoint>替换为从前提条件中获取的接入点。 .addSpanProcessor(BatchSpanProcessor.builder( OtlpHttpSpanExporter.builder() .setEndpoint("<HTTP-endpoint>") // 例如 http://tracing-analysis-dc-hz.aliyuncs.com/adapt_xxxx@xxxx_xxxx@xxxx/api/otlp/traces .build()).build() ) .setResource(otelResource) .build(); OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); // 获取tracer,用来创建Span。 tracer = openTelemetry.getTracer("android-tracer", "1.0.0");
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidKotlinDemo/app/src/main/java/com/example/androidkotlindemo/OpenTelemetryUtil.kt */ val otelResource = Resource.getDefault().merge( Resource.create( Attributes.of( ResourceAttributes.SERVICE_NAME, "<your-service-name>", // 请将<your-service-name>替换为您的应用名。 ResourceAttributes.HOST_NAME, "<your-host-name>" // 请将<your-host-name>替换为您的主机名。 ) ) ) /* 使用HTTP协议上报链路数据 */ val sdkTracerProvider = SdkTracerProvider.builder() .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) // 可选,将链路数据打印到日志/命令行,如不需要请注释这一行。 // 请将<HTTP-endpoint>替换为从前提条件中获取的接入点。 .addSpanProcessor(BatchSpanProcessor.builder( OtlpHttpSpanExporter.builder() .setEndpoint("<HTTP-endpoint>") // 例如:http://tracing-analysis-dc-hz.aliyuncs.com/adapt_xxxx@xxxx_xxxx@xxxx/api/otlp/traces .build()).build() ) .setResource(otelResource) .build(); val openTelemetry: OpenTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal() // 获取tracer,用来创建Span。 tracer = openTelemetry.getTracer("android-tracer", "1.0.0")
在应用程序初始化时对OpenTelemetry进行初始化。
在MainActivity的onCreate方法中调用OpenTelemetryUtil.init()方法。
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/java/com/example/androidjavademo/MainActivity.java */ ... public class MainActivity extends AppCompatActivity { ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 请添加这一行,对OpenTelemetry进行初始化。 OpenTelemetryUtil.init(); ... } ... }
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidKotlinDemo/app/src/main/java/com/example/androidkotlindemo/MainActivity.kt */ ... class MainActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) // 请添加这一行,OpenTelemetry初始化。 OpenTelemetryUtil.init() ... } }
步骤三:创建Span追踪链路数据
创建Span。
在FirstFragment文件的按钮点击事件监听方法中,创建一个名为First Fragment Button onClick的Span。
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/java/com/example/androidjavademo/FirstFragment.java */ public void onClick(View view) { // 获取Tracer Tracer tracer = OpenTelemetryUtil.getTracer(); // 创建Span Span span = tracer.spanBuilder("First Fragment Button onClick").startSpan(); try (Scope scope = span.makeCurrent()) { // 获取traceId System.out.println(span.getSpanContext().getTraceId()); ... } finally { span.end(); } }
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidKotlinDemo/app/src/main/java/com/example/androidkotlindemo/FirstFragment.kt */ binding.buttonFirst.setOnClickListener { // 获取Tracer val tracer: Tracer = OpenTelemetryUtil.getTracer()!! // 创建Span val span = tracer.spanBuilder("First Fragment Button onClick").startSpan() try { span.makeCurrent().use { scope -> // 获取traceId println(span.spanContext.traceId) // 获取spanId println(span.spanContext.spanId) findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) } } catch (t: Throwable) { span.setStatus(StatusCode.ERROR, "Something wrong in onClick") throw t } finally { span.end() } }
为Span设置属性和事件(Event)。
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/java/com/example/androidjavademo/FirstFragment.java */ // 设置属性 span.setAttribute("key", "value"); Attributes eventAttributes = Attributes.of( AttributeKey.stringKey("key"), "value", AttributeKey.longKey("result"), 0L); // 添加事件 span.addEvent("onClick", eventAttributes);
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidKotlinDemo/app/src/main/java/com/example/androidkotlindemo/FirstFragment.kt */ // 设置属性 span.setAttribute("key", "value") val eventAttributes = Attributes.of( AttributeKey.stringKey("key"), "value", AttributeKey.longKey("result"), 0L ) // 添加事件 span.addEvent("onClick", eventAttributes)
为Span设置状态(Status)。
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/java/com/example/androidjavademo/FirstFragment.java */ ... try (Scope scope = span.makeCurrent()) { ... } catch (Throwable t) { // 设置Span状态 span.setStatus(StatusCode.ERROR, "Something wrong in onClick"); throw t; } finally { span.end(); }
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidKotlinDemo/app/src/main/java/com/example/androidkotlindemo/FirstFragment.kt */ ... try { ... } catch (t: Throwable) { // 为Span设置状态 span.setStatus(StatusCode.ERROR, "Something wrong in onClick") throw t } finally { span.end() }
创建嵌套的Span。
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidJavaDemo/app/src/main/java/com/example/androidjavademo/FirstFragment.java */ public void parentSpan() { // 获取Tracer Tracer tracer = OpenTelemetryUtil.getTracer(); // 创建Span Span span = tracer.spanBuilder("Parent Span").startSpan(); try (Scope scope = span.makeCurrent()) { // 获取traceId System.out.println(span.getSpanContext().getTraceId()); // 获取spanId System.out.println(span.getSpanContext().getSpanId()); childSpan(); } finally { span.end(); } } public void childSpan() { // 获取Tracer Tracer tracer = OpenTelemetryUtil.getTracer(); // 创建Span Span span = tracer.spanBuilder("Child Span").startSpan(); try (Scope scope = span.makeCurrent()) { // 获取traceId System.out.println(span.getSpanContext().getTraceId()); // 获取spanId System.out.println(span.getSpanContext().getSpanId()); } finally { span.end(); } }
/** 请访问以下链接获取完整代码 https://github.com/alibabacloud-observability/android-demo/blob/master/AndroidKotlinDemo/app/src/main/java/com/example/androidkotlindemo/FirstFragment.kt */ // 嵌套Span fun parentSpan() { // 获取Tracer val tracer: Tracer = OpenTelemetryUtil.getTracer()!! // 创建Span val span = tracer.spanBuilder("Parent Span").startSpan() try { span.makeCurrent().use { scope -> // 获取traceId println(span.spanContext.traceId) // 获取spanId println(span.spanContext.spanId) childSpan() } } finally { span.end() } } // 嵌套Span fun childSpan() { // 获取Tracer val tracer: Tracer = OpenTelemetryUtil.getTracer()!! // 创建Span val span = tracer.spanBuilder("Child Span").startSpan() try { span.makeCurrent().use { scope -> // 获取traceId println(span.spanContext.traceId) // 获取spanId println(span.spanContext.spanId) } } finally { span.end() } }
步骤四:运行项目查看上报的链路数据
运行项目,单击Demo应用页面中的按钮。
通过logcat查看日志,可以看到通过LoggingSpanExporter导出的Span信息。
在可观测链路 OpenTelemetry 版控制台的应用列表页面选择目标应用,查看链路数据。
步骤五:打通客户端与服务端应用链路
修改Header中的Trace透传格式。
不同协议使用不同的HTTP Header向下游传递Trace上下文,如OpenTelemtry默认使用W3C Trace Context格式(也支持修改为其他格式),而Zipkin使用B3或B3 Multi格式。关于透传格式的更多信息,请参见OpenTelemetry指定透传Header格式。
根据服务端使用的协议类型,在客户端中设置对应的透传格式(textPropagators),以实现Android客户端应用与服务端应用链路打通:
如果服务端使用OpenTelemetry默认的W3C Trace Context格式,客户端无需设置textPropagators。
如果服务端使用Zipkin的B3/B3 Multi格式,客户端的textPropagators需要设置为B3Propagator。
// 设置B3透传格式。 OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(TextMapPropagator.composite( B3Propagator.injectingMultiHeaders(), B3Propagator.injectingSingleHeader()))) .buildAndRegisterGlobal();
如果服务端使用Jaeger协议,客户端的textPropagators需设置为JaegerPropagator。
// 设置Jaeger透传格式。 OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(TextMapPropagator.composite( JaegerPropagator.getInstance()))) .buildAndRegisterGlobal();
也可以同时设置多种Trace透传格式。
// 同时使用W3C Trace Context、B3、Jaeger三种Trace透传格式。 // 设置Jaeger透传格式。 OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) .setPropagators(ContextPropagators.create(TextMapPropagator.composite( W3CTraceContextPropagator.getInstance(), B3Propagator.injectingMultiHeaders(), B3Propagator.injectingSingleHeader() JaegerPropagator.getInstance()))) .buildAndRegisterGlobal();
导入okhttp3和opentelemetry-okhttp。
opentelemetry-okhttp-3.0是OpenTelemetry提供的针对OkHttp的自动埋点插件,可以自动拦截所有通过OkHttp3发出的网络请求并创建调用链。
在build.gradle中添加以下两个依赖。
dependencies { ... implementation 'io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0:2.3.0-alpha' implementation 'com.squareup.okhttp3:okhttp:4.12.0' }
创建OkHttpConfiguration。
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.okhttp.v3_0.OkHttpTelemetry; import okhttp3.Call; import okhttp3.OkHttpClient; public class OkHttpConfiguration { //Use this Call.Factory implementation for making standard http client calls. public Call.Factory createTracedClient(OpenTelemetry openTelemetry) { return OkHttpTelemetry.builder(openTelemetry).build().newCallFactory(createClient()); } //your configuration of the OkHttpClient goes here: private OkHttpClient createClient() { return new OkHttpClient.Builder().build(); } }
使用OkHttp3进行网络请求,访问服务端。
private void callHttpService() throws IOException { Tracer tracer = OpenTelemetryUtil.getTracer(); // 创建Span Span span = tracer.spanBuilder("AsyncRequestZipkinServer").startSpan(); System.out.println("AsyncRequestZipkinServer TraceID: " + span.getSpanContext().getTraceId()); System.out.println("AsyncRequestZipkinServer SpanID: " + span.getSpanContext().getSpanId()); try (Scope scope = span.makeCurrent()) { // 执行网络请求,例如使用 OkHttp OkHttpConfiguration configuration = new OkHttpConfiguration(); Call.Factory tracedClient = configuration.createTracedClient(GlobalOpenTelemetry.get()); Request request = new Request.Builder().url("${服务端地址}").get().build(); Call call = tracedClient.newCall(request); try (Response response = call.execute()) { // 处理响应 String responseBody = response.body().string(); System.out.println(responseBody); } catch (IOException e) { // 处理错误 e.printStackTrace(); } } finally { span.end(); } }
在调用链分析页面查看客户端与服务端打通的调用链。
如下图所示,
AsyncRequestZipkinServer
为Android应用,zipkin-demo-server
为服务端应用。