OpenTelemetry可用于测量采集遥测数据的代码。有关更多详细信息,请访问OpenTelemetry Website

希望使用OpenTelemetry导出遥测数据的必须依赖opentelemetry-api包,并且绝不可以配置或依赖 OpenTelemetry SDK。
SDK配置必须由“应用程序”提供,而该应用程序取决于opentelemetry-sdk软件包,或任何其他OpenTelemetry API的实现。这样,
库只有用户应用程序做相应配置的情况下才会获得真正的实现。有关更多详细信息,请查阅Library Guidelines

追踪(Tracing)

我们介绍如何使用OpenTelemetry API追踪代码。注意:永远不要调用OpenTelemetry SDK的方法。

首先必须获取一个Tracer,该Tracer负责创建spans和并与上下文交互。Tracer是通过使用OpenTelemetry API来获取的,该API指定了检测要监控的检查库或应用程序的库的名称和版本。可在规范章节 Obtaining a Tracer中获得更多信息。

1
2
Tracer tracer =
OpenTelemetry.getTracer("instrumentation-library-name","semver:1.0.0");

创建基础Span

要创建基础Span,只需指定Span的名称。 Span的开始和结束时间由OpenTelemetry SDK自动设置。

1
2
3
4
5
6
7
8
9
10
Span span = tracer.spanBuilder("my span").startSpan();
try (Scope scope = tracer.withSpan(span)) {
// your use case
...
} catch (Throwable t) {
Status status = Status.UNKNOWN.withDescription("Change it to your error message");
span.setStatus(status);
} finally {
span.end(); // closing the scope does not end the span, this has to be done manually
}

创建嵌套Span

很多时候我们希望为嵌套操作关联span。OpenTelemetry支持在进程内和跨远程进程进行追踪。更多关于如何在远程进程间共享上下文的详细信息,请查看上下文传播

对于方法a调用方法b,可以通过以下方式手动链接span:

1
2
3
4
5
6
7
8
9
10
11
12
13
void a() {
Span parentSpan = tracer.spanBuilder("a")
.startSpan();
b(parentSpan);
parentSpan.end();
}
void b(Span parentSpan) {
Span childSpan = tracer.spanBuilder("b")
.setParent(parentSpan)
.startSpan();
// do stuff
childSpan.end();
}

OpenTelemetry API还提供了一种自动方法来传播parentSpan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void a() {
Span parentSpan = tracer.spanBuilder("a").startSpan();
try(Scope scope = tracer.withSpan(parentSpan)){
b();
} finally {
parentSpan.end();
}
}
void b() {
Span childSpan = tracer.spanBuilder("b")
// NOTE: setParent(parentSpan) is not required anymore,
// `tracer.getCurrentSpan()` is automatically added as parent
.startSpan();
// do stuff
childSpan.end();
}

链接来自远程进程的span,只需将Remote Context设置为父级即可。

1
Span childRemoteParent = tracer.spanBuilder("Child").setParent(remoteContext).startSpan();

Span属性

在OpenTelemetry中,可以自由创建Span,由实现者使用特定于所表示操作的属性对其进行注释。属性在Span上提供有关它追踪的特定操作的附加上下文,比如结果或操作属性。

1
2
3
Span span = tracer.spanBuilder("/resource/path").setSpanKind(Span.Kind.CLIENT).startSpan();
span.setAttribute("http.method", "GET");
span.setAttribute("http.url", url.toString());

其中一些操作表示使用众所周知的协议(如HTTP或数据库调用)的调用。
对于这些,OpenTelemetry需要设置特定的属性。完整的属性列表在跨语言规范的Semantic Conventions中提供。

创建带事件的Span

Span 可以携带零个或多个Span属性的命名事件进行注释,每一个事件都是一个key:value键值对并自动携带相应的时间戳。

1
2
3
span.addEvent("Init");
...
span.addEvent("End");
1
2
3
4
5
Attributes eventAttributes = Attributes.of(
"key", AttributeValue.stringAttributeValue("value"),
"result", AttributeValue.longAttributeValue(0L));

span.addEvent("End Computation", eventAttributes);

创建带链接Span

一个Span可以连接一个或多个因果相关的其他Span。链接可用于表示批处理操作,其中一个Span的初始化由多个Span初始化构成,其中每个Span表示批处理中处理的单个输入项。

1
2
3
4
5
6
7
8
Link link1 = SpanData.Link.create(parentSpan1.getContext());
Link link2 = SpanData.Link.create(parentSpan2.getContext());
Span child = tracer.spanBuilder("childWithLink")
.addLink(link1)
.addLink(link2)
.addLink(parentSpan3.getContext())
.addLink(remoteContext)
.startSpan();

有关如何从远程进程中读取上下文的更多详细信息,请参见上下文传播

上下文传播

进程内传播依靠 gRPC Context
一个完善的上下文传播库,包含在一个小构件中,它不依赖于整个gRPC引擎。

OpenTelemetry提供了一种基于文本的方法,可以使用 W3C Trace Context HTTP标头。
以下是使用HttpURLConnection发出的HTTP请求的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Tell OpenTelemetry to inject the context in the HTTP headers
HttpTextFormat.Setter<HttpURLConnection> setter =
new HttpTextFormat.Setter<HttpURLConnection>() {
@Override
public void put(HttpURLConnection carrier, String key, String value) {
// Insert the context as Header
carrier.setRequestProperty(key, value);
}
};

URL url = new URL("http://127.0.0.1:8080/resource");
Span outGoing = tracer.spanBuilder("/resource").setSpanKind(Span.Kind.CLIENT).startSpan();
try (Scope scope = tracer.withSpan(outGoing)) {
// Semantic Convention.
// (Observe that to set these, Span does not *need* to be the current instance.)
outGoing.setAttribute("http.method", "GET");
outGoing.setAttribute("http.url", url.toString());
HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();
// Inject the request with the *current* Context, which contains our current Span.
OpenTelemetry.getPropagators().getHttpTextFormat().inject(Context.current(), transportLayer, setter);
// Make outgoing call
} finally {
outGoing.end();
}
...

类似的基于文本的方法可用于从传入请求中读取W3C追踪上下文。
下面提供了使用以下命令处理传入HTTP请求的示例 HttpExchange

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
HttpTextFormat.Getter<HttpExchange> getter =
new HttpTextFormat.Getter<HttpExchange>() {
@Override
public String get(HttpExchange carrier, String key) {
if (carrier.getRequestHeaders().containsKey(key)) {
return carrier.getRequestHeaders().get(key).get(0);
}
return null;
}
};
...
public void handle(HttpExchange httpExchange) {
// Extract the SpanContext and other elements from the request.
Context extractedContext = OpenTelemetry.getPropagators().getHttpTextFormat()
.extract(Context.current(), httpExchange, getter);
Span serverSpan = null;
try (Scope scope = ContextUtils.withScopedContext(extractedContext)) {
// Automatically use the extracted SpanContext as parent.
serverSpan = tracer.spanBuilder("/resource").setSpanKind(Span.Kind.SERVER)
.startSpan();
// Add the attributes defined in the Semantic Conventions
serverSpan.setAttribute("http.method", "GET");
serverSpan.setAttribute("http.scheme", "http");
serverSpan.setAttribute("http.host", "localhost:8080");
serverSpan.setAttribute("http.target", "/resource");
// Serve the request
...
} finally {
if (serverSpan != null) {
serverSpan.end();
}
}
}

指标(Metrics)

Span是获取应用程序正在执行的操作的详细信息的好方法,但是,对于更多聚合的场景哪?OpenTelemetry为指标提供支持,
一个时序数据可能表示诸如CPU利用率,HTTP服务器的请求计数或业务指标,例如交易。

所有指标都可以用标签进行注释:附加的限定有助于描述度量指标的细分。

以下是计数器用法的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Gets or creates a named meter instance
Meter meter = OpenTelemetry.getMeter("instrumentation-library-name","semver:1.0.0");

// Build counter e.g. LongCounter
LongCounter counter = meter
.longCounterBuilder("processed_jobs")
.setDescription("Processed jobs")
.setUnit("1")
.build();

// It is recommended that the API user keep a reference to a Bound Counter for the entire time or
// call unbind when no-longer needed.
BoundLongCounter someWorkCounter = counter.bind("Key", "SomeWork");

// Record data
someWorkCounter.add(123);

// Alternatively, the user can use the unbounded counter and explicitly
// specify the labels set at call-time:
counter.add(123, "Key", "SomeWork");

Observer是支持异步API并按需收集度量数据的附加工具,按间隔收集数据。

以下是观察者用法的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Build observer e.g. LongObserver
LongObserver observer = meter
.observerLongBuilder("cpu_usage")
.setDescription("CPU Usage")
.setUnit("ms")
.build();

observer.setCallback(
new LongObserver.Callback<LongObserver.ResultLongObserver>() {
@Override
public void update(ResultLongObserver result) {
// long getCpuUsage()
result.observe(getCpuUsage(), "Key", "SomeWork");
}
});

Tracing SDK配置

本文档中的配置示例仅适用于opentelemetry-sdk提供的SDK。API的其他实现可能提供不同的配置机制。

该应用程序必须安装带有输出器的span处理器,并且可以自定义OpenTelemetry SDK的行为。

比如一个基本配置实例化了SDK追踪器注册表,并设置为将追踪数据导出到日志记录流。

1
2
3
4
5
6
7
8
// Get the tracer
TracerSdkProvider tracerProvider = OpenTelemetrySdk.getTracerProvider();

// Set to export the traces to a logging stream
tracerProvider.addSpanProcessor(
SimpleSpanProcessor.newBuilder(
new LoggingSpanExporter()
).build());

采样器(Sampler)

追踪和导出应用程序中的每个用户请求并不总是可行的。
为了在可观察性和占用资源之间取得平衡,可以对追踪数据进行采样。
OpenTelemetry SDK提供了三个开箱即用的采样器:

  • AlwaysOnSampler不管上游采样决定如何,都对每个追踪进行采样。
  • AlwaysOffSampler不会对任何追踪进行采样,无论上游采样决定如何。
  • Probability 对上游的任何采样的追踪数据,根据配置的百分比进行采样。

可以通过实现io.opentelemetry.sdk.trace.Sampler接口来提供其他采样器。

1
2
3
4
5
6
7
8
9
10
11
12
13
TraceConfig alwaysOn = TraceConfig.getDefault().toBuilder().setSampler(
Samplers.alwaysOn()
).build();
TraceConfig alwaysOff = TraceConfig.getDefault().toBuilder().setSampler(
Samplers.alwaysOff()
).build();
TraceConfig half = TraceConfig.getDefault().toBuilder().setSampler(
Samplers.probability(0.5)
).build();
// Configure the sampler to use
tracerProvider.updateActiveTraceConfig(
half
);

Span处理器

OpenTelemetry提供了不同的Span处理器。SimpleSpanProcessor立即将结束的Span转发到导出器,而BatchSpanProcessor对它们进行批处理并批量发送它们。MultiSpanProcessor将多个Span处理器配置为同时处于活动状态。

1
2
3
4
5
6
7
8
9
10
tracerProvider.addSpanProcessor(
SimpleSpanProcessor.newBuilder(new LoggingSpanExporter()).build()
);
tracerProvider.addSpanProcessor(
BatchSpanProcessor.newBuilder(new LoggingSpanExporter()).build()
);
tracerProvider.addSpanProcessor(MultiSpanProcessor.create(Arrays.asList(
SimpleSpanProcessor.newBuilder(new LoggingSpanExporter()).build(),
BatchSpanProcessor.newBuilder(new LoggingSpanExporter()).build()
)));

导出器(Exporter)

Span处理器由导出器初始化,该导出器负责将遥测数据发送到特定的后端。OpenTelemetry提供了四种开箱即用的导出器:

  • In-Memory: 数据保存在内存中,在debug时使用。
  • Jaeger Exporter: 准备收集的遥测数据并将其通过gRPC发送到Jaeger后端。
  • Zipkin Exporter: 准备收集的遥测数据,并通过Zipkin API将其发送到Zipkin后端。
  • Logging Exporter: 将遥测数据保存到日志流中。
  • OpenTelemetry Exporter: 将数据发送到OpenTelemetry Collector (还未实现)。

其他导出器可以在OpenTelemetry Registry中找到。

1
2
3
4
5
6
7
8
9
10
11
12
13
tracerProvider.addSpanProcessor(
SimpleSpanProcessor.newBuilder(InMemorySpanExporter.create()).build());
tracerProvider.addSpanProcessor(
SimpleSpanProcessor.newBuilder(new LoggingSpanExporter()).build());

ManagedChannel jaegerChannel =
ManagedChannelBuilder.forAddress([ip:String], [port:int]).usePlaintext().build();
JaegerGrpcSpanExporter jaegerExporter = JaegerGrpcSpanExporter.newBuilder()
.setServiceName("example").setChannel(jaegerChannel).setDeadline(30000)
.build();
tracerProvider.addSpanProcessor(BatchSpanProcessor.newBuilder(
jaegerExporter
).build());

英文原文:https://github.com/open-telemetry/opentelemetry-java/blob/master/QUICKSTART.md