ARMS用户体验监控支持以用户会话作为切入点,追踪用户与应用交互过程中的错误、缓慢、异常问题,通过与ARMS应用监控联动,实现端到端分析,帮助您打通问题分析链路。本文介绍移动端监控如何实现端到端Trace打通。
前提条件
支持的Trace协议类型
Skywalking:v2版本(Skywalking 6.*,7.*)、v3版本(Skywalking 8.*)
接入操作
添加自身服务域名并开启Trace透传
- 登录ARMS控制台。
在左侧导航栏选择
,并在顶部菜单栏选择目标地域。单击目标应用名称,然后单击应用设置。
在请求管理区域配置自身服务域名。
重要请勿将第三方服务域名添加为自身服务,否则Trace打通可能不会生效。
添加自身服务域名,打开链路打通开关,并选择后端服务对应的透传协议。
说明后端应用需要先接入ARMS链路追踪,具体操作,请参见接入指南。
透传格式名称
格式
sw6: {sample}-{trace-id}-{segment-id}-{0}-{instance}-{entrance- instance-instance}-{address}
sw8: {sample}-{trace-id}-{segment-id}-{0}-{service}-{instance}-{endpoint}-{peer}
单击确认,系统将会自动推送Trace打通配置到移动端。
重要端侧配置的生效,可能会有分钟级延迟,具体取决于用户侧行为,SDK默认在App冷启动时会拉取一次配置,切至后台超过30s后再次打开时,也会重新拉取。
需要注意,虽然移动端不存在Web的CORS策略限制,但各协议透传的Request Header需要服务端网关支持透传,否则后端服务无法接受Header,也就无法实现RUM与Trace打通。
验证是否配置成功
此处以SkyWalking V3版本透传协议为例验证Trace是否配置成功。
Android
可通过Android Studio内置的App Inspection工具查看网络请求的Request Headers中是否携带了sw8协议信息判断是否接入成功。
iOS
可通过Xcode Profile工具(在Xcode工具栏选择
)查看网络请求的Request Headers中是否携带了sw8协议信息判断是否接入成功。配置后端应用Trace串联
为了打通完整Trace链路,还需要对后端应用进行配置,目前支持的后端应用类型以及接入方式如下:
Java应用
使用ARMS应用监控探针接入
ARMS应用监控探针默认集成了对OpenTelemetry协议的支持,因此无需额外配置,即可实现与RUM Trace关联,但需要确保以下两点:
支持的ARMS应用监控探针版本:2.x、3.x、4.x,为了获得更好的使用体验,推荐升级到4.x版本。
支持的Web容器和框架:支持Tomcat、Jetty、WebLogic、Undertow等主流Web容器,以及SpringBoot、SpringMVC等框架。支持的完整组件和框架请参见ARMS应用监控支持的Java组件和框架。
ARMS应用监控探针接入操作请参见开始监控Java应用。
使用OpenTelemetry接入
通过OpenTelemetry接入ARMS(可观测链路 OpenTelemetry 版),目前分为两种方式:自动埋点和手动埋点。
自动埋点场景下,由于OpenTelemetry已经支持了绝大多数主流框架,因此,无需额外配置,即可实现与RUM Trace的串联。
说明OpenTelemetry支持的Java框架,请参见通过OpenTelemetry上报Java应用数据。
手动埋点场景下,需要通过OpenTelemetry SDK提供的扩展机制,实现与RUM Trace的串联,即从前端请求头(traceparent、tracesate)中,解析出Trace上下文,以下是Springboot场景代码示例:
引入OpenTelemetry的依赖项。
<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-api</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk-trace</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-extension-annotations</artifactId> <version>1.18.0</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-otlp</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-semconv</artifactId> <version>1.30.1-alpha</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId> <version>1.34.1</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-extension-incubator</artifactId> <version>1.35.0-alpha</version> </dependency>
在OpenTelemetry初始化时,添加W3C Propagator。
Resource resource = Resource.getDefault() .merge(Resource.create(Attributes.of( ResourceAttributes.SERVICE_NAME, "otel-demo", ResourceAttributes.HOST_NAME, "xxxx" ))); SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() .addSpanProcessor(BatchSpanProcessor.builder(OtlpHttpSpanExporter.builder() .setEndpoint("Your Endpoint") .addHeader("Authentication", "Your Token") .build()).build()) .setResource(resource) .build(); openTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) // 这里添加W3C Propagator .setPropagators(ContextPropagators.create( TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance())) ).buildAndRegisterGlobal(); // 这里需要使用扩展的Tracer tracer = ExtendedTracer.create(openTelemetry.getTracer("com.example.tracer", "1.0.0"));
在业务接口中,添加headers参数,并从请求头中解析Trace上下文,设置parent。
// Controller中添加请求header参数,用于解析Trace上下文 @RequestMapping("/test") public String test(@RequestHeader Map<String, String> headers) { Span span = OpenTelemetrySupport.getTracer() .spanBuilder("/test") // 从headers中解析parent span .setParentFrom(OpenTelemetrySupport.getContextPropagators(), headers) .setSpanKind(SpanKind.SERVER) .startSpan(); try (Scope scope = span.makeCurrent()) { // do something } catch (Throwable t) { span.setStatus(StatusCode.ERROR, "handle parent span error"); } finally { span.end(); } return "success"; }
使用Jaeger接入
Jaeger目前针对Web场景,提供了:手动埋点和通过Spring Cloud组件埋点两种方式。完整接入方式请参见通过Jaeger上报Java应用数据。
通过SpringCloud组件埋点情况下,无需额外配置即可与RUM Trace打通。
手动埋点场景下,需要自行从前端请求header中解析Trace上下文。代码配置示例如下:
引入依赖项。
<dependency> <groupId>io.jaegertracing</groupId> <artifactId>jaeger-client</artifactId> <version>最新版本</version> <!-- 确保使用最新的Jaeger版本 --> </dependency>
初始化Tracer。
请将
<endpoint>
替换成可观测链路 OpenTelemetry 版控制台 页面相应客户端和地域的接入点。// 将manualDemo替换为您的应用名称。 io.jaegertracing.Configuration config = new io.jaegertracing.Configuration("manualDemo"); io.jaegertracing.Configuration.SenderConfiguration sender = new io.jaegertracing.Configuration.SenderConfiguration(); // 将 <endpoint> 替换为控制台概览页面上相应客户端和地域的接入点。 sender.withEndpoint("<endpoint>"); config.withSampler(new io.jaegertracing.Configuration.SamplerConfiguration().withType("const").withParam(1)); config.withReporter(new io.jaegertracing.Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000)); GlobalTracer.register(config.getTracer());
在业务接口中创建Span,可参考以下代码进行Trace关联。
// Controller中添加请求header参数,由于解析Trace上下文 @RequestMapping("/test") public String test(@RequestHeader Map<String, String> headers) { Tracer tracer = GlobalTracer.get(); SpanContext parentCtx = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(headers)); Span span; if (parentCtx != null) { span = tracer.buildSpan("/test").asChildOf(parentCtx).start(); } else { span = tracer.buildSpan("/test").start(); } try (Scope ignored = tracer.activateSpan(span)) { tracer.activeSpan().setTag("methodName", "test"); // do something } catch (Throwable t) { TracingHelper.onError(e, span); throw e } finally { span.finish(); } return "success"; }
使用Zipkin接入
完整接入方式请参见通过Zipkin上报Java应用数据。
按照文档接入,然后在服务端代码中,从Request Header中解析Context,实现与RUM Trace串联。
// 从request header解析Context
extractor = tracing.propagation().extractor(Request::getHeader);
// convert that context to a span which you can name and add tags to
oneWayReceive = nextSpan(tracer, extractor.extract(request))
.name("process-request")
.kind(SERVER)
... add tags etc.
// start the server side and flush instead of finish
oneWayReceive.start().flush();
// you should not modify this span anymore as it is complete. However,
// you can create children to represent follow-up work.
next = tracer.newSpan(oneWayReceive.context()).name("step2").start();
使用SkyWalking接入
完整接入方式请参见Java Agent插件。
SkyWalking目前仅提供了Java Agent接入方式,您只需要按照上述文档接入,即可实现RUM与后端Trace串联,需要注意,SkyWalking目前存在v2、v3两个版本,和RUM侧配置的协议匹配要求如下:
sw8
(v3版本):对应SkyWalking 8.x版本探针。sw6
(v2版本):对应SkyWalking 6.x和7.x版本探针。
Go应用
通过OpenTelemetry接入
完整接入方式请参见通过OpenTelemetry上报Go应用数据。
按照文档接入,然后在HTTP请求Handler中,通过request context生成Span,即可实现与RUM Trace串联。
// 初始化tracer
tracer := otel.Tracer(common.TraceInstrumentationName)
// 通过request context生成span
handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
span := trace.SpanFromContext(ctx)
// do something
w.Write([]byte("Hello World"))
})
通过Jaeger接入
完整接入方式请参见通过Jaeger上报Go应用数据。
按照文档接入,然后从HTTP请求头中解析Span上下文,实现与RUM Trace串联,代码示例如下:
// 从HTTP对象解析出spanCtx。
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan("myspan", opentracing.ChildOf(spanCtx))
...
defer span.Finish()
通过Zipkin接入
完整接入方式请参见通过Zipkin上报Go应用数据。
按照文档接入,然后从HTTP请求头中解析Span上下文,实现与RUM Trace串联,代码示例如下:
// 初始化tracer
tracer, err := exampletracer.NewTracer("go-frontend", frontendPort)
// 通过request context生成span
router.Methods("GET").Path("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// retrieve span from context
span := zipkin.SpanFromContext(r.Context())
// add some tag
span.Tag("some_key", "some_value")
// do something...
span.Annotate(time.Now(), "some_event")
})
使用SkyWalking接入
完整接入方式请参见通过SkyWalking上报Go应用数据。
按照文档接入,推荐采用skywalking-go接入方式,该方式支持了主流的Web服务框架,如gin、go-restful、http、go-kratos v2、go-micro、go-resty等,无需修改代码即可实现与RUM Trace串联。
如果您希望手动从HTTP请求头中解析出Trace上下文,也可参考以下代码实现:
//Extract context from HTTP request header `sw8`
span, ctx, err := tracer.CreateEntrySpan(r.Context(), "/api/test", func(key string) (string, error) {
return r.Header.Get(key), nil
})
Python应用
通过OpenTelemetry接入
完整接入方式请参见通过OpenTelemetry上报Python应用数据。
按照文档接入,然后从HTTP请求头中解析Span上下文,实现与RUM Trace串联,代码示例如下:
// 初始化tracer
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
tracer = trace.get_tracer(__name__)
@app.route('/test')
def test():
headers = dict(request.headers)
// 从headers中解析trace context
carrier ={'traceparent': headers['Traceparent'], 'tracestate': headers['Tracestate']}
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
with tracer.start_span("test", context=ctx):
// do something
return "success"
通过Jaeger接入
完整接入方式请参见通过Jaeger上报Python应用数据。
按照文档接入,然后从HTTP请求头中解析Span上下文,实现与RUM Trace串联,代码示例如下:
import logging
from flask import Flask
from jaeger_client import Config
from opentracing.ext import tags
from opentracing.propagation import Format
## 初始化tracer
def init_tracer(service, scope_manager=None):
logging.getLogger('').handlers = []
logging.basicConfig(format='%(message)s', level=logging.DEBUG)
config = Config(
config={
'sampler': {
'type': 'const',
'param': 1,
},
'logging': True,
'reporter_batch_size': 1,
},
service_name=service,
scope_manager=scope_manager
)
return config.initialize_tracer()
## trace decorator
def trace(tracer, span_name):
def decorator(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
## 从headers中解析trace context
span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers)
span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
with tracer.start_active_span(span_name, child_of=span_ctx, tags=span_tags) as scope:
rv = f(*args, **kwargs)
return rv
return wrapped
return decorator
## api test example
@app.route('/test')
@trace(tracer, 'test')
def test():
return "success"
使用SkyWalking接入
完整接入方式请参见通过SkyWalking上报Python应用数据。
按照文档接入,然后从HTTP请求头中解析Span上下文,实现与RUM Trace串联,代码示例如下:
from skywalking import config, agent
from skywalking.trace.context import SpanContext, get_context
from skywalking.trace.carrier import CarrierItem
# 配置 SkyWalking,根据需要调整相关参数
config.init(agent_collector_backend_services='<endpoint>',
agent_authentication='<auth-token>')
agent.start()
# 示例 HTTP 请求处理函数,需要传入 HTTP 请求的 headers
def handle_request(headers):
# 从请求头 headers 中提取追踪信息
carrier_items = []
for item in SpanContext.make_carrier():
carrier_header = headers.get(item.key.lower())
if carrier_header:
carrier_items.append(CarrierItem(item.key, carrier_header))
carrier = SpanContext.make_carrier(carrier_items)
# 从 Carrier 中提取追踪上下文
context = get_context().extract(carrier)
# 创建一个新 Span 来处理请求
with get_context().new_entry_span(op='operation_name') as span:
# 在这里处理请求,并在结束时自动提交 Span
...
# 模拟接收到的 HTTP 请求 header 包含 sw8
incoming_headers = {
'sw8': '1-My40LjU=-MTY1MTcwNDI5OTk5OA==-xxxx-xx-x-x==', # 示例值,实际根据请求填写
# 其他 header...
}
# 调用函数处理请求
handle_request(incoming_headers)
查看端到端完整Trace数据
完成前后端Trace打通后,您可以在ARMS用户体验控制台查看前端请求对应的调用链。
单击查看调用链,可以看到请求的完整调用链路,以及应用拓扑。此时,可以结合RUM侧的请求明细数据,与后端Trace数据,分析错慢请求问题。
最上面的Span即代表RUM入口Span,根据端侧接入类型,分为以下几种:
Web & H5场景:应用名:rum-browser,Span名称前缀:
"browser.request:"
。小程序场景:应用名:rum-miniapp,Span名称前缀:
"miniapp.request: "
。移动端Android场景:应用名:rum-android,Span名称前缀:
"android.request: "
。移动端iOS场景:应用名:rum-ios,Span名称前缀:
"ios.request: "
。
同时,也可以通过拓扑图,直观地看到整个请求的上下游服务拓扑。