链路追踪
编辑链路追踪
编辑链路追踪是一组具有共同根的事务和Span。每个链路追踪都跟踪单个请求的完整过程。它描述了由单个逻辑操作引起的各个操作及其因果关系。
分布式链路追踪
编辑当链路追踪像微服务架构中常见的那样穿过多个服务时,它被称为分布式链路追踪。分布式链路追踪包含跨多个分布式组件的操作,跨越进程、网络和安全边界。
分布式链路追踪使您能够通过跟踪请求的整个过程来分析整个微服务架构的性能 — 从前端服务上的初始 Web 请求一直到后端服务上的数据库查询。
跟踪请求在服务中传播的情况,可以提供应用程序花费时间、发生错误以及形成瓶颈的端到端视图。分布式链路追踪消除了各个服务的数据孤岛,并揭示了服务边界之外发生的事情。
对于受支持的技术,分布式链路追踪是开箱即用的,无需额外配置。
分布式链路追踪的工作原理是将自定义的 traceparent
HTTP 标头注入到传出的请求中。此标头包含诸如 trace-id
之类的信息,用于标识当前链路追踪,以及 parent-id
,用于标识传入请求上的当前 Span 的父级或传出请求上的当前 Span。
当服务处理请求时,它会检查是否存在此 HTTP 标头。如果缺少,则服务会启动新的链路追踪。如果存在,则服务会确保将当前操作添加为现有链路追踪的子级,并继续传播该链路追踪。
在此示例中,Elastic 的 Ruby 代理与 Elastic 的 Java 代理通信。两者都支持 traceparent
标头,并且链路追踪数据已成功传播。
在此示例中,Elastic 的 Ruby 代理与 OpenTelemetry 的 Java 代理通信。两者都支持 traceparent
标头,并且链路追踪数据已成功传播。
在此示例中,链路追踪遇到一段不传播 traceparent
标头的中间件。分布式链路追踪结束,任何进一步的通信都将导致新的链路追踪。
所有 Elastic 代理现在都支持官方的 W3C 链路上下文规范和 traceparent
标头。有关最低要求的代理版本,请参见下表
代理名称 | 代理版本 |
---|---|
Go 代理 |
≥ |
Java 代理 |
≥ |
.NET 代理 |
≥ |
Node.js 代理 |
≥ |
PHP 代理 |
≥ |
Python 代理 |
≥ |
Ruby 代理 |
≥ |
RUM 代理 |
≥ |
较旧的 Elastic 代理使用唯一的 elastic-apm-traceparent
标头。出于向后兼容性的目的,新版本的 Elastic 代理仍然支持此标头。
应用程序 UI 的时间轴可视化提供了对每个应用程序链路追踪的可视化深入分析
对于受支持的技术,Elastic 代理会自动传播分布式链路追踪上下文。如果您的服务通过不同的、不受支持的协议进行通信,您可以使用每个代理的 API 手动将分布式链路追踪上下文从发送服务传播到接收服务。
traceparent
标头添加到传出请求发送服务必须将 traceparent
标头添加到传出请求。
不适用。
- 使用
StartTransaction
启动事务,或使用StartSpan
启动 Span。 - 获取活动的
TraceContext
。 - 将
TraceContext
发送到接收服务。
示例
代理将使用 URLSessions
自动将链路追踪标头注入到网络请求中,但如果您使用的是非标准网络库,则可能需要手动注入它们。它将使用 OpenTelemetry API 完成
- 创建
Setter
- 根据 Open Telemetry 标准创建
Span
- 将链路追踪上下文注入到标头字典
- 按照您的网络库的步骤完成网络请求。请确保在请求成功或失败时调用
span.end()
。
import OpenTelemetryApi import OpenTelemetrySdk struct BasicSetter: Setter { func set(carrier: inout [String: String], key: String, value: String) { carrier[key] = value } } let span : Span = ... let setter = BasicSetter() let propagator = W3CTraceContextPropagator() var headers = [String:String]() propagator.inject(spanContext: span.context, carrier: &headers, setter:setter) let request = URLRequest(...) request.allHTTPHeaderFields = headers ... // make network request span.end()
- 使用
startTransaction
启动事务,或使用startSpan
启动 Span。 - 使用
injectTraceHeaders
将traceparent
标头注入到请求对象中
手动检测 RPC 框架的示例
// Hook into a callback provided by the RPC framework that is called on outgoing requests public Response onOutgoingRequest(Request request) throws Exception { Span span = ElasticApm.currentSpan() .startSpan("external", "http", null) .setName(request.getMethod() + " " + request.getHost()); try (final Scope scope = transaction.activate()) { span.injectTraceHeaders((name, value) -> request.addHeader(name, value)); return request.execute(); } catch (Exception e) { span.captureException(e); throw e; } finally { span.end(); } }
- 使用
CurrentTransaction
或CurrentSpan
序列化活动事务或 Span 的分布式链路追踪上下文。 - 将序列化的上下文发送到接收服务。
示例
string outgoingDistributedTracingData = (Agent.Tracer.CurrentSpan?.OutgoingDistributedTracingData ?? Agent.Tracer.CurrentTransaction?.OutgoingDistributedTracingData)?.SerializeToString(); // Now send `outgoingDistributedTracingData` to the receiving service
- 使用
apm.startTransaction()
启动事务,或使用apm.startSpan()
启动 Span。 - 使用
currentTraceparent
获取已启动的事务/Span 的序列化traceparent
字符串。 - 编码
traceparent
并将其发送到常规请求中的接收服务。
使用原始 UDP 在两个服务 A 和 B 之间进行通信的示例
- 在客户端(即发送请求的一方)获取当前分布式链路追踪上下文。
- 将当前分布式链路追踪上下文序列化为请求的传输支持的格式,并将其发送到服务器端(即接收请求的一方)。
示例
- 使用
begin_transaction()
启动事务。 - 获取活动事务的
trace_parent
。 - 将
trace_parent
发送到接收服务。
示例
- 使用
with_span
启动 Span。 - 获取活动的
TraceContext
。 - 将
TraceContext
发送到接收服务。
traceparent
标头接收服务必须解析传入的 traceparent
标头,并启动新的事务或 Span 作为接收到的上下文的子级。
不适用。
- 使用
ParseTraceparentHeader
或ParseTracestateHeader
解析传入的TraceContext
。 - 使用
StartTransactionOptions
或StartSpanOptions
启动新的事务或 Span 作为传入事务的子级。
示例
// Receive incoming TraceContext traceContext, _ := apmhttp.ParseTraceparentHeader(r.Header.Get("Traceparent")) traceContext.State, _ = apmhttp.ParseTracestateHeader(r.Header["Tracestate"]...) opts := apm.TransactionOptions{ TraceContext: traceContext, } transaction := apm.DefaultTracer().StartTransactionOptions("GET /", "request", opts)
不适用。
- 使用
startTransactionWithRemoteParent()
创建事务作为传入事务的子级。 - 使用
activate()
和setName()
启动并命名事务。
示例
// Hook into a callback provided by the framework that is called on incoming requests public Response onIncomingRequest(Request request) throws Exception { // creates a transaction representing the server-side handling of the request Transaction transaction = ElasticApm.startTransactionWithRemoteParent(request::getHeader, request::getHeaders); try (final Scope scope = transaction.activate()) { String name = "a useful name like ClassName#methodName where the request is handled"; transaction.setName(name); transaction.setType(Transaction.TYPE_REQUEST); return request.handle(); } catch (Exception e) { transaction.captureException(e); throw e; } finally { transaction.end(); } }
反序列化传入的分布式链路追踪上下文,并将其传递给任何 StartTransaction
或 CaptureTransaction
API — 所有这些 API 都有一个可选的 DistributedTracingData
参数。这将创建新的事务或 Span 作为传入链路追踪上下文的子级。
启动新事务示例
var transaction2 = Agent.Tracer.StartTransaction("Transaction2", "TestTransaction", DistributedTracingData.TryDeserializeFromString(serializedDistributedTracingData));
- 解码并在接收服务中存储
traceparent
。 - 将
traceparent
作为childOf
选项传递,以手动启动一个新事务,作为接收到的traceparent
的子事务,使用apm.startTransaction()
。
通过原始 UDP 接收 traceparent
的示例
- 在服务器端接收分布式追踪数据。
- 使用代理的公共 API 启动新事务。例如,使用
ElasticApm::beginCurrentTransaction
并将接收到的分布式追踪数据(序列化为字符串)作为参数传递。这将创建一个新事务,作为传入跟踪上下文的子事务。 - 不要忘记最终在服务器端结束事务。
示例
$receiverTransaction = ElasticApm::beginCurrentTransaction( 'GET /data-api', 'data-layer', /* timestamp */ null, $distDataAsString );
一旦在接收服务中创建了这个新事务,你就可以创建子 span,或者像通常一样使用任何其他代理 API 方法。
- 从字符串或 HTTP 标头创建
TraceParent
对象。 - 通过传入
TraceParent
对象,启动一个新事务,作为TraceParent
的子事务。
使用 HTTP 标头的示例
parent = elasticapm.trace_parent_from_headers(headers_dict) client.begin_transaction('processors', trace_parent=parent)
请参阅 TraceParent
API 获取更多示例。
使用 with_transaction
或 with_span
,启动一个新事务或 span,作为传入事务或 span 的子事务。
示例
可能需要一些额外的设置才能将请求与 Real User Monitoring (RUM) 代理正确关联。
请参阅 RUM 分布式追踪指南,了解有关启用跨域请求、设置服务器配置以及使用动态生成的 HTML 的信息。