使用 API 记录遥测数据
API 是一组用于跨关键可观测性信号记录遥测数据的类和接口。 SDK 是 API 的内置参考实现,配置用于处理和导出遥测数据。此页面是 API 的概念概述,包括描述、相关 Javadoc 链接、制品坐标和示例 API 用法。
API 包含以下顶级组件
- Context:一个独立的 API,用于在应用程序内以及跨应用程序边界传播上下文,包括跟踪上下文和 baggage。
- TracerProvider:跟踪的 API 入口点。
- MeterProvider:指标的 API 入口点。
- LoggerProvider:日志的 API 入口点。
- OpenTelemetry:顶级 API 组件(即
TracerProvider、MeterProvider、LoggerProvider、ContextPropagators)的容器,方便传递给 instrumentation。
API 设计支持多种实现。OpenTelemetry 提供两种实现
- SDK 参考实现。这是大多数用户的正确选择。
- 无操作 (No-op) 实现。一个极简的、零依赖的实现,供 instrumentation 在用户未安装实例时默认使用。
API 的设计可供库、框架和应用程序所有者直接依赖。它提供强大的向后兼容性保证、零传递依赖,并支持 Java 8+。库和框架应仅依赖 API 并仅调用 API 的方法,并指示应用程序/最终用户添加对 SDK 的依赖并安装配置好的实例。
所有 OpenTelemetry Java 组件的 Javadoc 参考,请参阅 javadoc.io/doc/io.opentelemetry。
API 组件
以下各节描述了 OpenTelemetry API。每个组件部分都包括
- 简要描述,包括指向 Javadoc 类型参考的链接。
- 相关资源链接,用于理解 API 方法和参数。
- API 用法的简单探索。
上下文 API
io.opentelemetry:opentelemetry-api-context:1.57.0 制品包含独立的 API(即与 OpenTelemetry API 分开打包),用于在应用程序内以及跨应用程序边界传播上下文。
它包含
- Context:一个不可变的键值对集合,在应用程序中隐式或显式地传播。
- ContextStorage:用于存储和检索当前上下文的机制,默认使用线程局部。
- ContextPropagators:用于传播跨应用程序边界的
Context的注册 propagators 的容器。
io.opentelemetry:opentelemetry-extension-kotlint:1.57.0 是一个扩展,提供了用于将上下文传播到协程的工具。
上下文
Context 是一个不可变的键值对集合,并包含用于在应用程序和跨线程中隐式传播的实用工具。隐式传播意味着可以在不显式作为参数传递的情况下访问上下文。Context 是 OpenTelemetry API 中一个反复出现的概念。
- 当前活动的 Span 存储在上下文中,默认情况下,span 的父级设置为当前上下文中的 span。
- 记录到 metric instruments 的测量值接受上下文参数,用于通过 exemplars 将测量值链接到 spans,并默认为当前上下文中的 span。
- LogRecords 接受上下文参数,用于链接 log record spans,并默认为当前上下文中的 span。
以下代码片段探索了 Context API 的用法
package otel;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.Scope;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ContextUsage {
public static void contextUsage() throws Exception {
// Define an example context key
ContextKey<String> exampleContextKey = ContextKey.named("example-context-key");
// Context doesn't contain the key until we add it
// Context.current() accesses the current context
// output => current context value: null
System.out.println("current context value: " + Context.current().get(exampleContextKey));
// Add entry to context
Context context = Context.current().with(exampleContextKey, "value");
// The local context var contains the added value
// output => context value: value
System.out.println("context value: " + context.get(exampleContextKey));
// The current context still doesn't contain the value
// output => current context value: null
System.out.println("current context value: " + Context.current().get(exampleContextKey));
// Calling context.makeCurrent() sets Context.current() to the context until the scope is
// closed, upon which Context.current() is restored to the state prior to when
// context.makeCurrent() was called. The resulting Scope implements AutoCloseable and is
// normally used in a try-with-resources block. Failure to call Scope.close() is an error and
// may cause memory leaks or other issues.
try (Scope scope = context.makeCurrent()) {
// The current context now contains the added value
// output => context value: value
System.out.println("context value: " + Context.current().get(exampleContextKey));
}
// The local context var still contains the added value
// output => context value: value
System.out.println("context value: " + context.get(exampleContextKey));
// The current context no longer contains the value
// output => current context value: null
System.out.println("current context value: " + Context.current().get(exampleContextKey));
ExecutorService executorService = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
// Context instances can be explicitly passed around application code, but it's more convenient
// to use implicit context, calling Context.makeCurrent() and accessing via Context.current().
// Context provides a number of utilities for implicit context propagation. These utilities wrap
// utility classes like Scheduler, ExecutorService, ScheduledExecutorService, Runnable,
// Callable, Consumer, Supplier, Function, etc and modify their behavior to call
// Context.makeCurrent() before running.
context.wrap(ContextUsage::callable).call();
context.wrap(ContextUsage::runnable).run();
context.wrap(executorService).submit(ContextUsage::runnable);
context.wrap(scheduledExecutorService).schedule(ContextUsage::runnable, 1, TimeUnit.SECONDS);
context.wrapConsumer(ContextUsage::consumer).accept(new Object());
context.wrapConsumer(ContextUsage::biConsumer).accept(new Object(), new Object());
context.wrapFunction(ContextUsage::function).apply(new Object());
context.wrapSupplier(ContextUsage::supplier).get();
}
/** Example {@link java.util.concurrent.Callable}. */
private static Object callable() {
return new Object();
}
/** Example {@link Runnable}. */
private static void runnable() {}
/** Example {@link java.util.function.Consumer}. */
private static void consumer(Object object) {}
/** Example {@link java.util.function.BiConsumer}. */
private static void biConsumer(Object object1, Object object2) {}
/** Example {@link java.util.function.Function}. */
private static Object function(Object object) {
return object;
}
/** Example {@link java.util.function.Supplier}. */
private static Object supplier() {
return new Object();
}
}
ContextStorage
ContextStorage 是用于存储和检索当前 Context 的机制。
默认的 ContextStorage 实现将 Context 存储在线程局部。
ContextPropagators
ContextPropagators 是用于传播跨应用程序边界的 Context 的注册 propagators 的容器。当上下文离开应用程序时(例如,出站 HTTP 请求),它会被注入到 carrier 中;当上下文进入应用程序时(例如,服务 HTTP 请求),它会从 carrier 中提取。
有关 propagator 实现,请参阅 SDK TextMapPropagators。
以下代码片段探索了用于注入的 ContextPropagators API
package otel;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class InjectContextUsage {
private static final TextMapSetter<HttpRequest.Builder> TEXT_MAP_SETTER = new HttpRequestSetter();
public static void injectContextUsage() throws Exception {
// Create a ContextPropagators instance which propagates w3c trace context and w3c baggage
ContextPropagators propagators =
ContextPropagators.create(
TextMapPropagator.composite(
W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance()));
// Create an HttpRequest builder
HttpClient httpClient = HttpClient.newBuilder().build();
HttpRequest.Builder requestBuilder =
HttpRequest.newBuilder().uri(new URI("http://127.0.0.1:8080/resource")).GET();
// Given a ContextPropagators instance, inject the current context into the HTTP request carrier
propagators.getTextMapPropagator().inject(Context.current(), requestBuilder, TEXT_MAP_SETTER);
// Send the request with the injected context
httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.discarding());
}
/** {@link TextMapSetter} with a {@link HttpRequest.Builder} carrier. */
private static class HttpRequestSetter implements TextMapSetter<HttpRequest.Builder> {
@Override
public void set(HttpRequest.Builder carrier, String key, String value) {
if (carrier == null) {
return;
}
carrier.setHeader(key, value);
}
}
}
以下代码片段探索了用于提取的 ContextPropagators API
package otel;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class ExtractContextUsage {
private static final TextMapGetter<HttpExchange> TEXT_MAP_GETTER = new HttpRequestGetter();
public static void extractContextUsage() throws Exception {
// Create a ContextPropagators instance which propagates w3c trace context and w3c baggage
ContextPropagators propagators =
ContextPropagators.create(
TextMapPropagator.composite(
W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance()));
// Create a server, which uses the propagators to extract context from requests
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/path", new Handler(propagators));
server.setExecutor(null);
server.start();
}
private static class Handler implements HttpHandler {
private final ContextPropagators contextPropagators;
private Handler(ContextPropagators contextPropagators) {
this.contextPropagators = contextPropagators;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
// Extract the context from the request and make the context current
Context extractedContext =
contextPropagators
.getTextMapPropagator()
.extract(Context.current(), exchange, TEXT_MAP_GETTER);
try (Scope scope = extractedContext.makeCurrent()) {
// Do work with the extracted context
} finally {
String response = "success";
exchange.sendResponseHeaders(200, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes(StandardCharsets.UTF_8));
os.close();
}
}
}
/** {@link TextMapSetter} with a {@link HttpExchange} carrier. */
private static class HttpRequestGetter implements TextMapGetter<HttpExchange> {
@Override
public Iterable<String> keys(HttpExchange carrier) {
return carrier.getRequestHeaders().keySet();
}
@Override
public String get(HttpExchange carrier, String key) {
if (carrier == null) {
return null;
}
List<String> headers = carrier.getRequestHeaders().get(key);
if (headers == null || headers.isEmpty()) {
return null;
}
return headers.get(0);
}
}
}
OpenTelemetry API
io.opentelemetry:opentelemetry-api:1.57.0 制品包含 OpenTelemetry API,包括 traces、metrics、logs、no-op 实现、baggage、key TextMapPropagator 实现,以及对 context API 的依赖。
Provider 和 Scope
Provider 和 Scope 是 OpenTelemetry API 中反复出现的概念。Scope 是应用程序内的逻辑单元,遥测数据与之相关联。Provider 提供与特定 Scope 相关的遥测记录组件。
- TracerProvider 提供作用域的 Tracers,用于记录 spans。
- MeterProvider 提供作用域的 Meters,用于记录指标。
- LoggerProvider 提供作用域的 Loggers,用于记录日志。
虽然 LoggerProvider / Logger API 在结构上与相应的 trace 和 metric API 类似,但它们服务于不同的用例。目前,LoggerProvider / Logger 及相关类代表 Log Bridge API,用于将通过其他 log API / 框架记录的日志桥接到 OpenTelemetry。它们无意作为 Log4j / SLF4J / Logback / 等的替代品供最终用户使用。
Scope 由(名称、版本、schemaUrl)三元组标识。务必确保 Scope 标识的唯一性。典型方法是将 Scope 名称设置为包名或完全限定类名,并将 Scope 版本设置为库版本。如果发出多个信号(即 metrics 和 traces)的遥测,应使用相同的 Scope。有关详细信息,请参阅 instrumentation scope。
以下代码片段探索了 provider 和 scope API 的用法
package otel;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.TracerProvider;
public class ProvidersAndScopes {
private static final String SCOPE_NAME = "fully.qualified.name";
private static final String SCOPE_VERSION = "1.0.0";
private static final String SCOPE_SCHEMA_URL = "https://example";
public static void providersUsage(OpenTelemetry openTelemetry) {
// Access providers from an OpenTelemetry instance
TracerProvider tracerProvider = openTelemetry.getTracerProvider();
MeterProvider meterProvider = openTelemetry.getMeterProvider();
// NOTE: LoggerProvider is a special case and should only be used to bridge logs from other
// logging APIs / frameworks into OpenTelemetry.
LoggerProvider loggerProvider = openTelemetry.getLogsBridge();
// Access tracer, meter, logger from providers to record telemetry for a particular scope
Tracer tracer =
tracerProvider
.tracerBuilder(SCOPE_NAME)
.setInstrumentationVersion(SCOPE_VERSION)
.setSchemaUrl(SCOPE_SCHEMA_URL)
.build();
Meter meter =
meterProvider
.meterBuilder(SCOPE_NAME)
.setInstrumentationVersion(SCOPE_VERSION)
.setSchemaUrl(SCOPE_SCHEMA_URL)
.build();
Logger logger =
loggerProvider
.loggerBuilder(SCOPE_NAME)
.setInstrumentationVersion(SCOPE_VERSION)
.setSchemaUrl(SCOPE_SCHEMA_URL)
.build();
// ...optionally, shorthand versions are available if scope version and schemaUrl aren't
// available
tracer = tracerProvider.get(SCOPE_NAME);
meter = meterProvider.get(SCOPE_NAME);
logger = loggerProvider.get(SCOPE_NAME);
}
}
Attributes
Attributes 是一个键值对集合,代表 attribute definition。Attributes 是 OpenTelemetry API 中反复出现的概念。
- Spans、span events 和 span links 具有 attributes。
- 记录到 metric instruments 的测量值具有 attributes。
- LogRecords 具有 attributes。
有关由语义约定生成的属性常量,请参阅 semantic attributes。
有关属性命名指南,请参阅 attribute naming。
以下代码片段探索了 Attributes API 的用法
package otel;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import java.util.Map;
public class AttributesUsage {
// Establish static constant for attribute keys and reuse to avoid allocations
private static final AttributeKey<String> SHOP_ID = AttributeKey.stringKey("com.acme.shop.id");
private static final AttributeKey<String> SHOP_NAME =
AttributeKey.stringKey("com.acme.shop.name");
private static final AttributeKey<Long> CUSTOMER_ID =
AttributeKey.longKey("com.acme.customer.id");
private static final AttributeKey<String> CUSTOMER_NAME =
AttributeKey.stringKey("com.acme.customer.name");
public static void attributesUsage() {
// Use a varargs initializer and pre-allocated attribute keys. This is the most efficient way to
// create attributes.
Attributes attributes =
Attributes.of(
SHOP_ID,
"abc123",
SHOP_NAME,
"opentelemetry-demo",
CUSTOMER_ID,
123L,
CUSTOMER_NAME,
"Jack");
// ...or use a builder.
attributes =
Attributes.builder()
.put(SHOP_ID, "abc123")
.put(SHOP_NAME, "opentelemetry-demo")
.put(CUSTOMER_ID, 123)
.put(CUSTOMER_NAME, "Jack")
// Optionally initialize attribute keys on the fly
.put(AttributeKey.stringKey("com.acme.string-key"), "value")
.put(AttributeKey.booleanKey("com.acme.bool-key"), true)
.put(AttributeKey.longKey("com.acme.long-key"), 1L)
.put(AttributeKey.doubleKey("com.acme.double-key"), 1.1)
.put(AttributeKey.stringArrayKey("com.acme.string-array-key"), "value1", "value2")
.put(AttributeKey.booleanArrayKey("come.acme.bool-array-key"), true, false)
.put(AttributeKey.longArrayKey("come.acme.long-array-key"), 1L, 2L)
.put(AttributeKey.doubleArrayKey("come.acme.double-array-key"), 1.1, 2.2)
// Optionally omit initializing AttributeKey
.put("com.acme.string-key", "value")
.put("com.acme.bool-key", true)
.put("come.acme.long-key", 1L)
.put("come.acme.double-key", 1.1)
.put("come.acme.string-array-key", "value1", "value2")
.put("come.acme.bool-array-key", true, false)
.put("come.acme.long-array-key", 1L, 2L)
.put("come.acme.double-array-key", 1.1, 2.2)
.build();
// Attributes has a variety of methods for manipulating and reading data.
// Read an attribute key:
String shopIdValue = attributes.get(SHOP_ID);
// Inspect size:
int size = attributes.size();
boolean isEmpty = attributes.isEmpty();
// Convert to a map representation:
Map<AttributeKey<?>, Object> map = attributes.asMap();
// Iterate through entries, printing each to the template: <key> (<type>): <value>\n
attributes.forEach(
(attributeKey, value) ->
System.out.printf(
"%s (%s): %s%n", attributeKey.getKey(), attributeKey.getType(), value));
// Convert to a builder, remove the com.acme.customer.id and any entry whose key starts with
// com.acme.shop, and build a new instance:
AttributesBuilder builder = attributes.toBuilder();
builder.remove(CUSTOMER_ID);
builder.removeIf(attributeKey -> attributeKey.getKey().startsWith("com.acme.shop"));
Attributes trimmedAttributes = builder.build();
}
}
OpenTelemetry
Spring Boot Starter 是一个特殊情况,其中 OpenTelemetry 可作为 Spring bean 使用。只需将 OpenTelemetry 注入到您的 Spring 组件中即可。
OpenTelemetry 是顶级 API 组件的容器,方便传递给 instrumentation。
OpenTelemetry 包含
- TracerProvider:跟踪的 API 入口点。
- MeterProvider:指标的 API 入口点。
- LoggerProvider:日志的 API 入口点。
- ContextPropagators:上下文传播的 API 入口点。
以下代码片段探索了 OpenTelemetry API 的用法
package otel;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.context.propagation.ContextPropagators;
public class OpenTelemetryUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void openTelemetryUsage(OpenTelemetry openTelemetry) {
// Access TracerProvider, MeterProvider, LoggerProvider, ContextPropagators
TracerProvider tracerProvider = openTelemetry.getTracerProvider();
MeterProvider meterProvider = openTelemetry.getMeterProvider();
LoggerProvider loggerProvider = openTelemetry.getLogsBridge();
ContextPropagators propagators = openTelemetry.getPropagators();
}
}
GlobalOpenTelemetry
Java Agent 是一个特殊情况,其中 GlobalOpenTelemetry 由 Agent 设置。只需调用 GlobalOpenTelemetry.getOrNoop() 即可访问 OpenTelemetry 实例。
有关 使用自定义手动 instrumentation 扩展 Java Agent 的更多信息。
GlobalOpenTelemetry 存储一个全局单例 OpenTelemetry 实例。
GlobalOpenTelemetry 的设计方式非常独特,以避免初始化顺序问题,因此应谨慎使用。具体来说,无论是否调用了 GlobalOpenTelemetry.set(..),GlobalOpenTelemetry.get() 始终返回相同的结果。内部而言,如果在调用 set() 之前调用 get(),实现将内部调用 set(..) 并使用 no-op implementation 并返回它。由于 set(..) 在被调用一次以上时会触发异常,因此在 get() 之后调用 set(..) 会导致异常,而不是默默失败。
Java Agent 代表一种特殊情况:GlobalOpenTelemetry 是 native instrumentation 和 manual instrumentation 将遥测数据记录到 Agent 安装的 OpenTelemetry 实例的唯一机制。使用此实例很重要且有用,我们建议如下访问 GlobalOpenTelemetry:
对于 native instrumentation,默认使用 GlobalOpenTelemetry.getOrNoop()
package otel;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
public class GlobalOpenTelemetryNativeInstrumentationUsage {
public static void globalOpenTelemetryUsage(OpenTelemetry openTelemetry) {
// Initialized with OpenTelemetry from java agent if present, otherwise no-op implementation.
MyClient client1 = new MyClientBuilder().build();
// Initialized with an explicit OpenTelemetry instance, overriding the java agent instance.
MyClient client2 = new MyClientBuilder().setOpenTelemetry(openTelemetry).build();
}
/**
* An example library with native OpenTelemetry instrumentation, initialized via {@link
* MyClientBuilder}.
*/
public static class MyClient {
private final OpenTelemetry openTelemetry;
private MyClient(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
// ... library methods omitted
}
/** Builder for {@link MyClient}. */
public static class MyClientBuilder {
// OpenTelemetry defaults to the GlobalOpenTelemetry instance if set, e.g. by the java agent or
// by the application, else to a no-op implementation.
private OpenTelemetry openTelemetry = GlobalOpenTelemetry.getOrNoop();
/** Explicitly set the OpenTelemetry instance to use. */
public MyClientBuilder setOpenTelemetry(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
return this;
}
/** Build the client. */
public MyClient build() {
return new MyClient(openTelemetry);
}
}
}
请注意,GlobalOpenTelemetry.getOrNoop() 的设计不具有 get() 调用 set(..) 的副作用,保留了应用程序代码以后调用 set(..) 而不触发异常的能力。
结果是
- 如果存在 Java Agent,则 instrumentation 默认使用 Agent 安装的
OpenTelemetry实例进行初始化。 - 如果不存在 Java Agent,则 instrumentation 默认使用 no-op 实现进行初始化。
- 用户可以通过调用
setOpenTelemetry(..)并使用单独的实例来显式覆盖默认值。
对于 manual instrumentation,默认使用
package otel;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
public class GlobalOpenTelemetryManualInstrumentationUsage {
public static void globalOpenTelemetryUsage() {
// If GlobalOpenTelemetry is already set, e.g. by the java agent, use it.
// Else, initialize an OpenTelemetry SDK instance and use it.
OpenTelemetry openTelemetry =
GlobalOpenTelemetry.isSet() ? GlobalOpenTelemetry.get() : initializeOpenTelemetry();
// Install into manual instrumentation. This may involve setting as a singleton in the
// application's dependency injection framework.
}
/** Initialize OpenTelemetry SDK using autoconfiguration. */
public static OpenTelemetry initializeOpenTelemetry() {
return AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
}
}
结果是
- 如果存在 Java Agent,则应用程序使用 Agent 安装的
OpenTelemetry实例初始化 manual instrumentation。 - 如果不存在 Java Agent,则应用程序初始化一个 OpenTelemetrySdk 实例并使用它来初始化 manual instrumentation。
TracerProvider
TracerProvider 是 traces 的 API 入口点,并提供 Tracers。有关 providers 和 scopes 的信息,请参阅 providers and scopes。
Tracer
Tracer 用于为 instrumentation scope 记录 spans。有关 providers 和 scopes 的信息,请参阅 providers and scopes。
Span
SpanBuilder 和 Span 用于构造和记录 span 的数据。
SpanBuilder 用于在调用 Span startSpan() 之前向 span 添加数据。可以在启动后通过调用各种 Span 更新方法来添加/更新数据。在启动前提供给 SpanBuilder 的数据作为 Samplers 的输入。
以下代码片段探索了 SpanBuilder / Span API 的用法
package otel;
import static io.opentelemetry.context.Context.current;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import java.util.Arrays;
public class SpanUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void spanUsage(Tracer tracer) {
// Get a span builder by providing the span name
Span span =
tracer
.spanBuilder("span name")
// Set span kind
.setSpanKind(SpanKind.INTERNAL)
// Set attributes
.setAttribute(AttributeKey.stringKey("com.acme.string-key"), "value")
.setAttribute(AttributeKey.booleanKey("com.acme.bool-key"), true)
.setAttribute(AttributeKey.longKey("com.acme.long-key"), 1L)
.setAttribute(AttributeKey.doubleKey("com.acme.double-key"), 1.1)
.setAttribute(
AttributeKey.stringArrayKey("com.acme.string-array-key"),
Arrays.asList("value1", "value2"))
.setAttribute(
AttributeKey.booleanArrayKey("come.acme.bool-array-key"),
Arrays.asList(true, false))
.setAttribute(
AttributeKey.longArrayKey("come.acme.long-array-key"), Arrays.asList(1L, 2L))
.setAttribute(
AttributeKey.doubleArrayKey("come.acme.double-array-key"), Arrays.asList(1.1, 2.2))
// Optionally omit initializing AttributeKey
.setAttribute("com.acme.string-key", "value")
.setAttribute("com.acme.bool-key", true)
.setAttribute("come.acme.long-key", 1L)
.setAttribute("come.acme.double-key", 1.1)
.setAllAttributes(WIDGET_RED_CIRCLE)
// Uncomment to optionally explicitly set the parent span context. If omitted, the
// span's parent will be set using Context.current().
// .setParent(parentContext)
// Uncomment to optionally add links.
// .addLink(linkContext, linkAttributes)
// Start the span
.startSpan();
// Check if span is recording before computing additional data
if (span.isRecording()) {
// Update the span name with information not available when starting
span.updateName("new span name");
// Add additional attributes not available when starting
span.setAttribute("com.acme.string-key2", "value");
// Add additional span links not available when starting
span.addLink(exampleLinkContext());
// optionally include attributes on the link
span.addLink(exampleLinkContext(), WIDGET_RED_CIRCLE);
// Add span events
span.addEvent("my-event");
// optionally include attributes on the event
span.addEvent("my-event", WIDGET_RED_CIRCLE);
// Record exception, syntactic sugar for a span event with a specific shape
span.recordException(new RuntimeException("error"));
// Set the span status
span.setStatus(StatusCode.OK, "status description");
}
// Finally, end the span
span.end();
}
/** Return a dummy link context. */
private static SpanContext exampleLinkContext() {
return Span.fromContext(current()).getSpanContext();
}
}
Span parenting 是 tracing 的一个重要方面。每个 span 都有一个可选的父级。通过收集 trace 中的所有 spans 并跟踪每个 span 的父级,我们可以构建一个层次结构。span API 构建在 context 之上,这允许 span context 在应用程序和跨线程中隐式传递。创建 span 时,其父级将设置为 Context.current() 中存在的 span,除非没有 span 或 context 被显式覆盖。
大多数 context API 用法指南都适用于 spans。Span context 通过 W3CTraceContextPropagator 和其他 TextMapPropagators 在应用程序边界之间传播。
以下代码片段探索了 Span API context propagation
package otel;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
public class SpanAndContextUsage {
private final Tracer tracer;
SpanAndContextUsage(Tracer tracer) {
this.tracer = tracer;
}
public void nestedSpanUsage() {
// Start a span. Since we don't call makeCurrent(), we must explicitly call setParent on
// children. Wrap code in try / finally to ensure we end the span.
Span span = tracer.spanBuilder("span").startSpan();
try {
// Start a child span, explicitly setting the parent.
Span childSpan =
tracer
.spanBuilder("span child")
// Explicitly set parent.
.setParent(span.storeInContext(Context.current()))
.startSpan();
// Call makeCurrent(), adding childSpan to Context.current(). Spans created inside the scope
// will have their parent set to childSpan.
try (Scope childSpanScope = childSpan.makeCurrent()) {
// Call another method which creates a span. The span's parent will be childSpan since it is
// started in the childSpan scope.
doWork();
} finally {
childSpan.end();
}
} finally {
span.end();
}
}
private int doWork() {
Span doWorkSpan = tracer.spanBuilder("doWork").startSpan();
try (Scope scope = doWorkSpan.makeCurrent()) {
int result = 0;
for (int i = 0; i < 10; i++) {
result += i;
}
return result;
} finally {
doWorkSpan.end();
}
}
}
MeterProvider
MeterProvider 是 metrics 的 API 入口点,并提供 Meters。有关 providers 和 scopes 的信息,请参阅 providers and scopes。
Meter
Meter 用于为特定的 instrumentation scope 获取 instruments。有关 providers 和 scopes 的信息,请参阅 providers and scopes。存在各种 instruments,每个 instruments 在 SDK 中都有不同的语义和默认行为。选择适合每个特定用例的 instrument 非常重要。
| Instrument | Sync or Async | 描述 | 示例 | Default SDK Aggregation |
|---|---|---|---|---|
| Counter | 同步 | 记录单调(正)值。 | 记录用户登录 | sum (monotonic=true) |
| Async Counter | 异步 | 观察单调和。 | 观察 JVM 中已加载类的数量 | sum (monotonic=true) |
| UpDownCounter | 同步 | 记录非单调(正数和负数)值。 | 记录项目添加到队列和从队列中移除时 | sum (monotonic=false) |
| Async UpDownCounter | 异步 | 观察非单调(正数和负数)和。 | 观察 JVM 内存池使用情况 | sum (monotonic=false) |
| Histogram | 同步 | 记录单调(正)值,其中分布很重要。 | 记录服务器处理的 HTTP 请求的持续时间 | ExplicitBucketHistogram |
| Gauge | 同步 | 记录最新值,其中空间重新聚合无意义 **[1]**。 | 记录温度 | LastValue |
| Async Gauge | 异步 | 观察最新值,其中空间重新聚合无意义 **[1]**。 | 观察 CPU 利用率 | LastValue |
[1]:空间重新聚合是通过删除不需要的属性来合并属性流的过程。例如,给定具有属性 {"color": "red", "shape": "square"}、{"color": "blue", "shape": "square"} 的序列,您可以删除 color 属性,并在删除 color 后属性相等的序列上合并,从而执行空间重新聚合。大多数聚合都有一个有用的空间聚合合并函数(例如,总和被加在一起),但由 LastValue 聚合的 gauges 是例外。例如,假设前面提到的序列正在跟踪小部件的温度。当您删除 color 属性时,如何合并序列?除了抛硬币并选择一个随机值之外,没有好的答案。
instrument API 具有各种共享功能
- 使用 builder 模式创建。
- 必需的 instrument 名称。
- 可选的单位和描述。
- 记录
long或double的值,这可以通过 builder 进行配置。
有关指标命名和单位的详细信息,请参阅 metric guidelines。
有关 instrument 库作者的额外指导,请参阅 guidelines for instrumentation library authors。
Counter
LongCounter 和 DoubleCounter 用于记录单调(正)值。
以下代码片段探索了 counter API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_SHAPE;
import static otel.Util.computeWidgetColor;
import static otel.Util.computeWidgetShape;
import static otel.Util.customContext;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
public class CounterUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void counterUsage(Meter meter) {
// Construct a counter to record measurements that are always positive (monotonically
// increasing).
LongCounter counter =
meter
.counterBuilder("fully.qualified.counter")
.setDescription("A count of produced widgets")
.setUnit("{widget}")
// optionally change the type to double
// .ofDoubles()
.build();
// Record a measurement with no attributes or context.
// Attributes defaults to Attributes.empty(), context to Context.current().
counter.add(1L);
// Record a measurement with attributes, using pre-allocated attributes whenever possible.
counter.add(1L, WIDGET_RED_CIRCLE);
// Sometimes, attributes must be computed using application context.
counter.add(
1L, Attributes.of(WIDGET_SHAPE, computeWidgetShape(), WIDGET_COLOR, computeWidgetColor()));
// Record a measurement with attributes, and context.
// Most users will opt to omit the context argument, preferring the default Context.current().
counter.add(1L, WIDGET_RED_CIRCLE, customContext());
}
}
Async Counter
ObservableLongCounter 和 ObservableDoubleCounter 用于观察单调(正)和。
以下代码片段探索了 async counter API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_SHAPE;
import static otel.Util.computeWidgetColor;
import static otel.Util.computeWidgetShape;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongCounter;
import java.util.concurrent.atomic.AtomicLong;
public class AsyncCounterUsage {
// Pre-allocate attributes whenever possible
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void asyncCounterUsage(Meter meter) {
AtomicLong widgetCount = new AtomicLong();
// Construct an async counter to observe an existing counter in a callback
ObservableLongCounter asyncCounter =
meter
.counterBuilder("fully.qualified.counter")
.setDescription("A count of produced widgets")
.setUnit("{widget}")
// Uncomment to optionally change the type to double
// .ofDoubles()
.buildWithCallback(
// the callback is invoked when a MetricReader reads metrics
observableMeasurement -> {
long currentWidgetCount = widgetCount.get();
// Record a measurement with no attributes.
// Attributes defaults to Attributes.empty().
observableMeasurement.record(currentWidgetCount);
// Record a measurement with attributes, using pre-allocated attributes whenever
// possible.
observableMeasurement.record(currentWidgetCount, WIDGET_RED_CIRCLE);
// Sometimes, attributes must be computed using application context.
observableMeasurement.record(
currentWidgetCount,
Attributes.of(
WIDGET_SHAPE, computeWidgetShape(), WIDGET_COLOR, computeWidgetColor()));
});
// Optionally close the counter to unregister the callback when required
asyncCounter.close();
}
}
UpDownCounter
LongUpDownCounter 和 DoubleUpDownCounter 用于记录非单调(正数和负数)值。
以下代码片段探索了 updowncounter API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_SHAPE;
import static otel.Util.computeWidgetColor;
import static otel.Util.computeWidgetShape;
import static otel.Util.customContext;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongUpDownCounter;
import io.opentelemetry.api.metrics.Meter;
public class UpDownCounterUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void usage(Meter meter) {
// Construct an updowncounter to record measurements that go up and down.
LongUpDownCounter upDownCounter =
meter
.upDownCounterBuilder("fully.qualified.updowncounter")
.setDescription("Current length of widget processing queue")
.setUnit("{widget}")
// Uncomment to optionally change the type to double
// .ofDoubles()
.build();
// Record a measurement with no attributes or context.
// Attributes defaults to Attributes.empty(), context to Context.current().
upDownCounter.add(1L);
// Record a measurement with attributes, using pre-allocated attributes whenever possible.
upDownCounter.add(-1L, WIDGET_RED_CIRCLE);
// Sometimes, attributes must be computed using application context.
upDownCounter.add(
-1L, Attributes.of(WIDGET_SHAPE, computeWidgetShape(), WIDGET_COLOR, computeWidgetColor()));
// Record a measurement with attributes, and context.
// Most users will opt to omit the context argument, preferring the default Context.current().
upDownCounter.add(1L, WIDGET_RED_CIRCLE, customContext());
}
}
Async UpDownCounter
ObservableLongUpDownCounter 和 ObservableDoubleUpDownCounter 用于观察非单调(正数和负数)和。
以下代码片段探索了 async updowncounter API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_SHAPE;
import static otel.Util.computeWidgetColor;
import static otel.Util.computeWidgetShape;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
import java.util.concurrent.atomic.AtomicLong;
public class AsyncUpDownCounterUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void asyncUpDownCounterUsage(Meter meter) {
AtomicLong queueLength = new AtomicLong();
// Construct an async updowncounter to observe an existing up down counter in a callback
ObservableLongUpDownCounter asyncUpDownCounter =
meter
.upDownCounterBuilder("fully.qualified.updowncounter")
.setDescription("Current length of widget processing queue")
.setUnit("{widget}")
// Uncomment to optionally change the type to double
// .ofDoubles()
.buildWithCallback(
// the callback is invoked when a MetricReader reads metrics
observableMeasurement -> {
long currentWidgetCount = queueLength.get();
// Record a measurement with no attributes.
// Attributes defaults to Attributes.empty().
observableMeasurement.record(currentWidgetCount);
// Record a measurement with attributes, using pre-allocated attributes whenever
// possible.
observableMeasurement.record(currentWidgetCount, WIDGET_RED_CIRCLE);
// Sometimes, attributes must be computed using application context.
observableMeasurement.record(
currentWidgetCount,
Attributes.of(
WIDGET_SHAPE, computeWidgetShape(), WIDGET_COLOR, computeWidgetColor()));
});
// Optionally close the counter to unregister the callback when required
asyncUpDownCounter.close();
}
}
Histogram
DoubleHistogram 和 LongHistogram 用于记录单调(正)值,其中分布很重要。
以下代码片段探索了 histogram API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_SHAPE;
import static otel.Util.computeWidgetColor;
import static otel.Util.computeWidgetShape;
import static otel.Util.customContext;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.Meter;
public class HistogramUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void histogramUsage(Meter meter) {
// Construct a histogram to record measurements where the distribution is important.
DoubleHistogram histogram =
meter
.histogramBuilder("fully.qualified.histogram")
.setDescription("Length of time to process a widget")
.setUnit("s")
// Uncomment to optionally provide advice on useful default explicit bucket boundaries
// .setExplicitBucketBoundariesAdvice(Arrays.asList(1.0, 2.0, 3.0))
// Uncomment to optionally change the type to long
// .ofLongs()
.build();
// Record a measurement with no attributes or context.
// Attributes defaults to Attributes.empty(), context to Context.current().
histogram.record(1.1);
// Record a measurement with attributes, using pre-allocated attributes whenever possible.
histogram.record(2.2, WIDGET_RED_CIRCLE);
// Sometimes, attributes must be computed using application context.
histogram.record(
3.2, Attributes.of(WIDGET_SHAPE, computeWidgetShape(), WIDGET_COLOR, computeWidgetColor()));
// Record a measurement with attributes, and context.
// Most users will opt to omit the context argument, preferring the default Context.current().
histogram.record(4.4, WIDGET_RED_CIRCLE, customContext());
}
}
Gauge
DoubleGauge 和 LongGauge 用于记录最新值,其中空间重新聚合无意义。
以下代码片段探索了 gauge API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_SHAPE;
import static otel.Util.computeWidgetColor;
import static otel.Util.computeWidgetShape;
import static otel.Util.customContext;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleGauge;
import io.opentelemetry.api.metrics.Meter;
public class GaugeUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void gaugeUsage(Meter meter) {
// Construct a gauge to record measurements as they occur, which cannot be spatially
// re-aggregated.
DoubleGauge gauge =
meter
.gaugeBuilder("fully.qualified.gauge")
.setDescription("The current temperature of the widget processing line")
.setUnit("K")
// Uncomment to optionally change the type to long
// .ofLongs()
.build();
// Record a measurement with no attributes or context.
// Attributes defaults to Attributes.empty(), context to Context.current().
gauge.set(273.0);
// Record a measurement with attributes, using pre-allocated attributes whenever possible.
gauge.set(273.0, WIDGET_RED_CIRCLE);
// Sometimes, attributes must be computed using application context.
gauge.set(
273.0,
Attributes.of(WIDGET_SHAPE, computeWidgetShape(), WIDGET_COLOR, computeWidgetColor()));
// Record a measurement with attributes, and context.
// Most users will opt to omit the context argument, preferring the default Context.current().
gauge.set(1L, WIDGET_RED_CIRCLE, customContext());
}
}
Async Gauge
ObservableDoubleGauge 和 ObservableLongGauge 用于观察最新值,其中空间重新聚合无意义。
以下代码片段探索了 async gauge API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_SHAPE;
import static otel.Util.computeWidgetColor;
import static otel.Util.computeWidgetShape;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableDoubleGauge;
import java.util.concurrent.atomic.AtomicReference;
public class AsyncGaugeUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void asyncGaugeUsage(Meter meter) {
AtomicReference<Double> processingLineTemp = new AtomicReference<>(273.0);
// Construct an async gauge to observe an existing gauge in a callback
ObservableDoubleGauge asyncGauge =
meter
.gaugeBuilder("fully.qualified.gauge")
.setDescription("The current temperature of the widget processing line")
.setUnit("K")
// Uncomment to optionally change the type to long
// .ofLongs()
.buildWithCallback(
// the callback is invoked when a MetricReader reads metrics
observableMeasurement -> {
double currentWidgetCount = processingLineTemp.get();
// Record a measurement with no attributes.
// Attributes defaults to Attributes.empty().
observableMeasurement.record(currentWidgetCount);
// Record a measurement with attributes, using pre-allocated attributes whenever
// possible.
observableMeasurement.record(currentWidgetCount, WIDGET_RED_CIRCLE);
// Sometimes, attributes must be computed using application context.
observableMeasurement.record(
currentWidgetCount,
Attributes.of(
WIDGET_SHAPE, computeWidgetShape(), WIDGET_COLOR, computeWidgetColor()));
});
// Optionally close the gauge to unregister the callback when required
asyncGauge.close();
}
}
LoggerProvider
LoggerProvider 是 logs 的 API 入口点,并提供 Loggers。有关 providers 和 scopes 的信息,请参阅 providers and scopes。
虽然 LoggerProvider / Logger API 在结构上与相应的 trace 和 metric API 类似,但它们服务于不同的用例。目前,LoggerProvider / Logger 及相关类代表 Log Bridge API,用于将通过其他 log API / 框架记录的日志桥接到 OpenTelemetry。它们无意作为 Log4j / SLF4J / Logback / 等的替代品供最终用户使用。
Logger
Logger 用于为 instrumentation scope emit log records。有关 providers 和 scopes 的信息,请参阅 providers and scopes。
LogRecordBuilder
LogRecordBuilder 用于构造和 emit log records。
以下代码片段探索了 LogRecordBuilder API 的用法
package otel;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.logs.Severity;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class LogRecordUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void logRecordUsage(Logger logger) {
logger
.logRecordBuilder()
// Set body. Note, setBody(..) is called multiple times for demonstration purposes but only
// the last call is used.
// Set the body to a string, syntactic sugar for setBody(Value.of("log message"))
.setBody("log message")
// Optionally set the body to a Value to record arbitrarily complex structured data
.setBody(Value.of("log message"))
.setBody(Value.of(1L))
.setBody(Value.of(1.1))
.setBody(Value.of(true))
.setBody(Value.of(new byte[] {'a', 'b', 'c'}))
.setBody(Value.of(Value.of("entry1"), Value.of("entry2")))
.setBody(
Value.of(
Map.of(
"stringKey",
Value.of("entry1"),
"mapKey",
Value.of(Map.of("stringKey", Value.of("entry2"))))))
// Set severity
.setSeverity(Severity.DEBUG)
.setSeverityText("debug")
// Set timestamp
.setTimestamp(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
// Optionally set the timestamp when the log was observed
.setObservedTimestamp(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
// Set attributes
.setAttribute(AttributeKey.stringKey("com.acme.string-key"), "value")
.setAttribute(AttributeKey.booleanKey("com.acme.bool-key"), true)
.setAttribute(AttributeKey.longKey("com.acme.long-key"), 1L)
.setAttribute(AttributeKey.doubleKey("com.acme.double-key"), 1.1)
.setAttribute(
AttributeKey.stringArrayKey("com.acme.string-array-key"),
Arrays.asList("value1", "value2"))
.setAttribute(
AttributeKey.booleanArrayKey("come.acme.bool-array-key"), Arrays.asList(true, false))
.setAttribute(AttributeKey.longArrayKey("come.acme.long-array-key"), Arrays.asList(1L, 2L))
.setAttribute(
AttributeKey.doubleArrayKey("come.acme.double-array-key"), Arrays.asList(1.1, 2.2))
.setAllAttributes(WIDGET_RED_CIRCLE)
// Uncomment to optionally explicitly set the context used to correlate with spans. If
// omitted, Context.current() is used.
// .setContext(context)
// Emit the log record
.emit();
}
}
无操作实现
OpenTelemetry#noop() 方法提供了对 OpenTelemetry 及其提供的所有 API 组件的 no-op 实现的访问。顾名思义,no-op 实现什么也不做,并且设计为对性能没有影响。即使在使用 no-op 时,如果 instrumentation 正在计算/分配属性值和其他记录遥测数据所需的数据,也可能对其性能产生影响。当用户未配置和安装像 SDK 这样的具体实现时,no-op 是一个有用的 OpenTelemetry 默认实例。
以下代码片段探索了 OpenTelemetry#noop() API 的用法
package otel;
import static otel.Util.WIDGET_COLOR;
import static otel.Util.WIDGET_RED_CIRCLE;
import static otel.Util.WIDGET_SHAPE;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.api.metrics.DoubleGauge;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.LongUpDownCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
public class NoopUsage {
private static final String SCOPE_NAME = "fully.qualified.name";
public static void noopUsage() {
// Access the no-op OpenTelemetry instance
OpenTelemetry noopOpenTelemetry = OpenTelemetry.noop();
// No-op tracing
Tracer noopTracer = OpenTelemetry.noop().getTracer(SCOPE_NAME);
noopTracer
.spanBuilder("span name")
.startSpan()
.setAttribute(WIDGET_SHAPE, "square")
.setStatus(StatusCode.OK)
.addEvent("event-name", Attributes.builder().put(WIDGET_COLOR, "red").build())
.end();
// No-op metrics
Attributes attributes = WIDGET_RED_CIRCLE;
Meter noopMeter = OpenTelemetry.noop().getMeter(SCOPE_NAME);
DoubleHistogram histogram = noopMeter.histogramBuilder("fully.qualified.histogram").build();
histogram.record(1.0, attributes);
// counter
LongCounter counter = noopMeter.counterBuilder("fully.qualified.counter").build();
counter.add(1, attributes);
// async counter
noopMeter
.counterBuilder("fully.qualified.counter")
.buildWithCallback(observable -> observable.record(10, attributes));
// updowncounter
LongUpDownCounter upDownCounter =
noopMeter.upDownCounterBuilder("fully.qualified.updowncounter").build();
// async updowncounter
noopMeter
.upDownCounterBuilder("fully.qualified.updowncounter")
.buildWithCallback(observable -> observable.record(10, attributes));
upDownCounter.add(-1, attributes);
// gauge
DoubleGauge gauge = noopMeter.gaugeBuilder("fully.qualified.gauge").build();
gauge.set(1.1, attributes);
// async gauge
noopMeter
.gaugeBuilder("fully.qualified.gauge")
.buildWithCallback(observable -> observable.record(10, attributes));
// No-op logs
Logger noopLogger = OpenTelemetry.noop().getLogsBridge().get(SCOPE_NAME);
noopLogger
.logRecordBuilder()
.setBody("log message")
.setAttribute(WIDGET_SHAPE, "square")
.setSeverity(Severity.INFO)
.emit();
}
}
语义属性
semantic conventions 描述了如何以标准化方式收集常见操作的遥测数据。这包括一个 attribute registry,它枚举了约定中引用的所有属性的定义,并按域进行组织。 semantic-conventions-java 项目从语义约定生成常量,这些常量可用于帮助 instrumentation 符合要求。
| 描述 | Artifact |
|---|---|
| 稳定语义约定的生成代码 | io.opentelemetry.semconv:opentelemetry-semconv:1.37.0-alpha |
| 孵化语义约定的生成代码 | io.opentelemetry.semconv:opentelemetry-semconv-incubating:1.37.0-alpha |
虽然 opentelemetry-semconv 和 opentelemetry-semconv-incubating 都包含 -alpha 后缀并且可能发生破坏性更改,但意图是稳定 opentelemetry-semconv 并永久保留 opentelemetry-semconv-incubating 上的 -alpha 后缀。库可以使用 opentelemetry-semconv-incubating 进行测试,但不应将其作为依赖项:由于属性可能在版本之间出现或消失,因此将其作为依赖项可能会使最终用户面临运行时错误,因为传递的版本冲突会发生。
从语义约定生成的属性常量是 AttributeKey<T> 的实例,可以在 OpenTelemetry API 接受属性的任何地方使用。
以下代码片段探索了语义约定属性 API 的用法
package otel;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.semconv.HttpAttributes;
import io.opentelemetry.semconv.ServerAttributes;
import io.opentelemetry.semconv.incubating.HttpIncubatingAttributes;
public class SemanticAttributesUsage {
public static void semanticAttributesUsage() {
// Semantic attributes are organized by top-level domain and whether they are stable or
// incubating.
// For example:
// - stable attributes starting with http.* are in the HttpAttributes class.
// - stable attributes starting with server.* are in the ServerAttributes class.
// - incubating attributes starting with http.* are in the HttpIncubatingAttributes class.
// Attribute keys which define an enumeration of values are accessible in an inner
// {AttributeKey}Values class.
// For example, the enumeration of http.request.method values is available in the
// HttpAttributes.HttpRequestMethodValues class.
Attributes attributes =
Attributes.builder()
.put(HttpAttributes.HTTP_REQUEST_METHOD, HttpAttributes.HttpRequestMethodValues.GET)
.put(HttpAttributes.HTTP_ROUTE, "/users/:id")
.put(ServerAttributes.SERVER_ADDRESS, "example")
.put(ServerAttributes.SERVER_PORT, 8080L)
.put(HttpIncubatingAttributes.HTTP_RESPONSE_BODY_SIZE, 1024)
.build();
}
}
Baggage
Baggage 是与分布式请求或工作流执行相关联的应用程序定义的键值对集合。Baggage 键和值都是字符串,值具有可选的字符串元数据。可以通过配置 SDK 将条目作为属性添加到 spans、metrics 和 log records 来丰富遥测数据。baggage API 构建在 context 之上,这允许 span context 在应用程序和跨线程中隐式传递。大多数 context API 用法指南都适用于 baggage。
Baggage 通过 W3CBaggagePropagator 在应用程序边界之间传播(有关详细信息,请参阅 TextMapPropagator)。
以下代码片段探索了 Baggage API 的用法
package otel;
import static io.opentelemetry.context.Context.current;
import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.baggage.BaggageEntry;
import io.opentelemetry.api.baggage.BaggageEntryMetadata;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Scope;
import java.util.Map;
import java.util.stream.Collectors;
public class BaggageUsage {
private static final Attributes WIDGET_RED_CIRCLE = Util.WIDGET_RED_CIRCLE;
public static void baggageUsage() {
// Access current baggage with Baggage.current()
// output => context baggage: {}
Baggage currentBaggage = Baggage.current();
System.out.println("current baggage: " + asString(currentBaggage));
// ...or from a Context
currentBaggage = Baggage.fromContext(current());
// Baggage has a variety of methods for manipulating and reading data.
// Convert to builder and add entries:
Baggage newBaggage =
Baggage.current().toBuilder()
.put("shopId", "abc123")
.put("shopName", "opentelemetry-demo", BaggageEntryMetadata.create("metadata"))
.build();
// ...or uncomment to start from empty
// newBaggage = Baggage.empty().toBuilder().put("shopId", "abc123").build();
// output => new baggage: {shopId=abc123(), shopName=opentelemetry-demo(metadata)}
System.out.println("new baggage: " + asString(newBaggage));
// Read an entry:
String shopIdValue = newBaggage.getEntryValue("shopId");
// Inspect size:
int size = newBaggage.size();
boolean isEmpty = newBaggage.isEmpty();
// Convert to map representation:
Map<String, BaggageEntry> map = newBaggage.asMap();
// Iterate through entries:
newBaggage.forEach((s, baggageEntry) -> {});
// The current baggage still doesn't contain the new entries
// output => context baggage: {}
System.out.println("current baggage: " + asString(Baggage.current()));
// Calling Baggage.makeCurrent() sets Baggage.current() to the baggage until the scope is
// closed, upon which Baggage.current() is restored to the state prior to when
// Baggage.makeCurrent() was called.
try (Scope scope = newBaggage.makeCurrent()) {
// The current baggage now contains the added value
// output => context baggage: {shopId=abc123(), shopName=opentelemetry-demo(metadata)}
System.out.println("current baggage: " + asString(Baggage.current()));
}
// The current baggage no longer contains the new entries:
// output => context baggage: {}
System.out.println("current baggage: " + asString(Baggage.current()));
}
private static String asString(Baggage baggage) {
return baggage.asMap().entrySet().stream()
.map(
entry ->
String.format(
"%s=%s(%s)",
entry.getKey(),
entry.getValue().getValue(),
entry.getValue().getMetadata().getValue()))
.collect(Collectors.joining(", ", "{", "}"));
}
}
孵化中的 API
io.opentelemetry:opentelemetry-api-incubator:1.57.0-alpha artifact 包含实验性的 trace、metric、log 和 context API。这些孵化中的 API 在次要版本中可能包含破坏性的 API 更改。通常,这些代表我们希望在获得用户反馈之前进行验证的实验性规范功能或 API 设计。我们鼓励用户试用这些 API,并就任何反馈(正面或负面)提出问题。库不应依赖于孵化中的 API,因为当发生传递版本冲突时,用户可能会遇到运行时错误。
有关可用 API 和示例用法,请参阅 incubator README。