Record Telemetry with API
The API is a set of classes and interfaces for recording telemetry across key observability signals. The SDK is the built-in reference implementation of the API, configured to process and export telemetry. This page is a conceptual overview of the API, including descriptions, links to relevant Javadocs, artifact coordinates, and sample API usage.
The API consists of the following top-level components:
- Context: A standalone API for propagating context throughout an application and across application boundaries, including trace context and baggage.
- TracerProvider: The API entry point for traces.
- MeterProvider: The API entry point for metrics.
- LoggerProvider: The API entry point for logs.
- OpenTelemetry: A holder for top-level API components (i.e.
TracerProvider
,MeterProvider
,LoggerProvider
,ContextPropagators
) which is convenient to pass to instrumentation.
The API is designed to support multiple implementations. Two implementations are provided by OpenTelemetry:
- SDK reference implementation. This is the right choice for most users.
- Noop implementation. A minimalist, zero-dependency implementation for instrumentations to use by default when the user doesn’t install an instance.
The API is designed to be taken as a direct dependency by libraries, frameworks, and application owners. It comes with strong backwards compatibility guarantees, zero transitive dependencies, and supports Java 8+. Libraries and frameworks should depend only on the API and only call methods from the API, and instruct applications / end users to add a dependency on the SDK and install a configured instance.
API Components
The following sections describe the OpenTelemetry API. Each component section includes:
- A brief description, including a link to the Javadoc type reference.
- Links to relevant resources to understand the API methods and arguments.
- A simple exploration of API usage.
Context API
The io.opentelemetry:opentelemetry-api-context:1.44.1
artifact contains standalone APIs (i.e. packaged separately from
OpenTelemetry API) for propagating context throughout an
application and across application boundaries.
It consists of:
- Context: An immutable bundle of key value pairs which is implicitly or explicitly propagated through an application.
- ContextStorage: A mechanism for storing and retrieving the current context, defaulting to thread local.
- ContextPropagators: A container of registered propagators for
propagating
Context
across application boundaries.
The io.opentelemetry:opentelemetry-extension-kotlint:1.44.1
is an extension with tools for propagating context into coroutines.
Context
Context is an immutable bundle of key value pairs, with utilities for implicitly propagating through an application and across threads. Implicit propagation means that the context can be accessed without explicitly passing it as an argument. Context is a recurring concept in the OpenTelemetry API:
- The current active Span is stored in context, and by default a span’s parent is assigned to whatever span is currently in context.
- The measurements recorded to metric instruments accept a context argument, used to link measurements to spans via exemplars and defaulting to whatever span is currently in context.
- LogRecords accept a context argument, used to link log record spans and defaulting to whatever span is currently in context.
The following code snippet explores Context
API usage:
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.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
is a mechanism for storing and retrieving the current Context
.
The default ContextStorage
implementation stores Context
in thread local.
ContextPropagators
ContextPropagators
is a container of registered propagators for propagating Context
across
application boundaries. Context is injected into a carrier when leaving an
application (i.e. an outbound HTTP request), and extracted from a carrier when
entering an application (i.e. serving an HTTP request).
See SDK TextMapPropagators for propagator implementations.
The following code snippet explores ContextPropagators
API for injection:
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);
}
}
}
The following code snippet explores ContextPropagators
API for extraction:
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
The io.opentelemetry:opentelemetry-api:1.44.1
artifact
contains the OpenTelemetry API, including traces, metrics, logs, noop
implementation, baggage, key TextMapPropagator
implementations, and a
dependency on the context API.
Providers and Scopes
Providers and scopes are recurring concepts in the OpenTelemetry API. A scope is a logical unit within the application which telemetry is associated with. A provider provides components for recording telemetry relative to a particular scope:
- TracerProvider provides scoped Tracers for recording spans.
- MeterProvider provides scoped Meters for recording metrics.
- LoggerProvider provides scoped Loggers for recording logs.
LoggerProvider
/ Logger
APIs are structurally similar to the equivalent trace and metric APIs, they serve a different use case. As of now, LoggerProvider
/ Logger
and associated classes represent the Log Bridge API, which exists to write log appenders to bridge logs recorded through other log APIs / frameworks into OpenTelemetry. They are not intended for end user use as a replacement for Log4j / SLF4J / Logback / etc.A scope is identified by the triplet (name, version, schemaUrl). Care must be taken to ensure the scope identity is unique. A typical approach is to set the scope name to the package name or fully qualified class name, and to set the scope version to the library version. If emitting telemetry for multiple signals (i.e. metrics and traces), the same scope should be used. See instrumentation scope for details.
The following code snippet explores provider and scope API usage:
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
is a bundle of key value pairs representing the
standard attribute definition.
Attributes
are a recurring concept in the OpenTelemetry API:
- Spans, span events, and span links have attributes.
- The measurements recorded to metric instruments have attributes.
- LogRecords have attributes.
See semantic attributes for attribute constants generated from the semantic conventions.
See attribute naming for guidance on attribute naming.
The following code snippet explores Attributes
API usage:
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
OpenTelemetry is a holder for top-level API components which is convenient to pass to instrumentation.
OpenTelemetry
consists of:
- TracerProvider: The API entry point for traces.
- MeterProvider: The API entry point for metrics.
- LoggerProvider: The API entry point for logs.
- ContextPropagators: The API entry point for context propagation.
The following code snippet explores OpenTelemetry
API usage:
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
GlobalOpenTelemetry holds a global singleton OpenTelemetry instance.
Instrumentation should avoid using GlobalOpenTelemetry
. Instead, accept
OpenTelemetry
as an initialization argument and default to the
Noop implementation if not set. There is an exception to
this rule: the OpenTelemetry
instance installed by the
Java agent is available via
GlobalOpenTelemetry
. Users with additional manual instrumentation are
encouraged to access it via GlobalOpenTelemetry.get()
.
GlobalOpenTelemetry.get()
is guaranteed to always return the same result. If
GlobalOpenTelemetry.get()
is called before GlobalOpenTelemetry.set(..)
,
GlobalOpenTelemetry
is set to the noop implementation and future calls to
GlobalOpenTelemetry.set(..)
throw an exception. Therefore, it’s critical to
call GlobalOpenTelemetry.set(..)
as early in the application lifecycle as
possible, and before GlobalOpenTelemetry.get()
is called by any
instrumentation. This guarantee surfaces initialization ordering issues: calling
GlobalOpenTelemetry.set()
too late (i.e. after instrumentation has called
GlobalOpenTelemetry.get()
) triggers an exception rather than silently failing.
If autoconfigure is present,
GlobalOpenTelemetry
can be automatically initialized by setting
-Dotel.java.global-autoconfigure.enabled=true
(or via env var
export OTEL_JAVA_GLOBAL_AUTOCONFIGURE_ENABLED=true
). When enabled, the first
call to GlobalOpenTelemetry.get()
triggers autoconfiguration and calls
GlobalOpenTelemetry.set(..)
with the resulting OpenTelemetry
instance.
The following code snippet explores GlobalOpenTelemetry
API context
propagation:
package otel;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
public class GlobalOpenTelemetryUsage {
public static void openTelemetryUsage(OpenTelemetry openTelemetry) {
// Set the GlobalOpenTelemetry instance as early in the application lifecycle as possible
// Set must only be called once. Calling multiple times raises an exception.
GlobalOpenTelemetry.set(openTelemetry);
// Get the GlobalOpenTelemetry instance.
openTelemetry = GlobalOpenTelemetry.get();
}
}
TracerProvider
TracerProvider is the API entry point for traces and provides Tracers. See providers and scopes for information on providers and scopes.
Tracer
Tracer is used to record spans for an instrumentation scope. See providers and scopes for information on providers and scopes.
Span
SpanBuilder and Span are used to construct and record data to spans.
SpanBuilder
is used to add data to a span before starting it by calling
Span startSpan()
. Data can be added / updated after starting by calling
various Span
update methods. The data provided to SpanBuilder
before
starting is provided as an input to Samplers.
The following code snippet explores SpanBuilder
/ Span
API usage:
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 is an important aspect of tracing. Each span has an optional
parent. By collecting all the spans in a trace and following each span’s parent,
we can construct a hierarchy. The span APIs are built on top of
context, which allows span context to be implicitly passed around an
application and across threads. When a span is created, its parent is set to the
whatever span is present in Context.current()
unless there is no span or the
context is explicitly overridden.
Most of the context API usage guidance applies to spans. Span context is propagated across application boundaries with the W3CTraceContextPropagator and other TextMapPropagators.
The following code snippet explores 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 is the API entry point for metrics and provides Meters. See providers and scopes for information on providers and scopes.
Meter
Meter is used to obtain instruments for a particular instrumentation scope. See providers and scopes for information on providers and scopes. There are a variety of instruments, each with different semantics and default behavior in the SDK. It’s important to choose the right instrument for each particular use case:
Instrument | Sync or Async | Description | Example | Default SDK Aggregation |
---|---|---|---|---|
Counter | sync | Record monotonic (positive) values. | Record user logins | sum (monotonic=true) |
Async Counter | async | Observe monotonic sums. | Observe number of classes loaded in the JVM | sum (monotonic=true) |
UpDownCounter | sync | Record non-monotonic (positive and negative) values. | Record when items are added to and removed from a queue | sum (monotonic=false) |
Async UpDownCounter | async | Observe non-monotonic (positive and negative) sums. | Observe JVM memory pool usage | sum (monotonic=false) |
Histogram | sync | Record monotonic (positive) values where the distribution is important. | Record duration of HTTP requests processed by server | ExplicitBucketHistogram |
Gauge | sync | Record the latest value where spatial re-aggregation does not make sense [1]. | Record temperature | LastValue |
Async Gauge | async | Observe the latest value where spatial re-aggregation does not make sense [1]. | Observe CPU utilization | LastValue |
[1]: Spatial re-aggregation is the process of merging attribute streams by
dropping attributes which are not needed. For example, given series with
attributes {"color": "red", "shape": "square"}
,
{"color": "blue", "shape": "square"}
, you can perform spatial re-aggregation
by dropping the color
attribute, and merging the series where the attributes
are equal after dropping color
. Most aggregations have a useful spatial
aggregation merge function (i.e. sums are summed together), but gauges
aggregated by the LastValue
aggregation are the exception. For example,
suppose the series mentioned previously are tracking the temperature of widgets.
How do you merge the series when you drop the color
attribute? There is no
good answer besides flipping a coin and selecting a random value.
The instrument APIs have share a variety of features:
- Created using the builder pattern.
- Required instrument name.
- Optional unit and description.
- Record values which are
long
ordouble
, which is configured via the builder.
See metric guidelines for details on metric naming and units.
See guidelines for instrumentation library authors for additional guidance on instrument selection.
Counter
LongCounter and DoubleCounter are used to record monotonic (positive) values.
The following code snippet explores counter API usage:
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 and ObservableDoubleCounter are used to observe monotonic (positive) sums.
The following code snippet explores async counter API usage:
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 and DoubleUpDownCounter are used to record non-monotonic (positive and negative) values.
The following code snippet explores updowncounter API usage:
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 and ObservableDoubleUpDownCounter are used to observe non-monotonic (positive and negative) sums.
The following code snippet explores async updowncounter API usage:
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 and LongHistogram are used to record monotonic (positive) values where the distribution is important.
The following code snippet explores histogram API usage:
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 and LongGauge are used to record the latest value where spatial re-aggregation does not make sense.
The following code snippet explores gauge API usage:
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 and ObservableLongGauge are used to observe the latest value where spatial re-aggregation does not make sense.
The following code snippet explores async gauge API usage:
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 counter to observe an existing counter 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 is the API entry point for logs and provides Loggers. See providers and scopes for information on providers and scopes.
LoggerProvider
/ Logger
APIs are structurally similar to the equivalent trace and metric APIs, they serve a different use case. As of now, LoggerProvider
/ Logger
and associated classes represent the Log Bridge API, which exists to write log appenders to bridge logs recorded through other log APIs / frameworks into OpenTelemetry. They are not intended for end user use as a replacement for Log4j / SLF4J / Logback / etc.Logger
Logger is used to emit log records for an instrumentation scope. See providers and scopes for information on providers and scopes.
LogRecordBuilder
LogRecordBuilder is used to construct and emit log records.
The following code snippet explores LogRecordBuilder
API usage:
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();
}
}
Noop implementation
The OpenTelemetry#noop()
method provides access to a noop implementation of
OpenTelemetry and all API components it provides access to. As
the name suggests, the noop implementation does nothing and is designed to have
no impact on performance. Instrumentation may see impact on performance even
when the noop is used if it is computing / allocating attribute values and other
data required to record the telemetry. The noop is a useful default instance of
OpenTelemetry
when a user has not configured and installed a concrete
implementation such as the SDK.
The following code snippet explores OpenTelemetry#noop()
API usage:
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 noop OpenTelemetry instance
OpenTelemetry noopOpenTelemetry = OpenTelemetry.noop();
// Noop 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();
// Noop 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));
// Noop logs
Logger noopLogger = OpenTelemetry.noop().getLogsBridge().get(SCOPE_NAME);
noopLogger
.logRecordBuilder()
.setBody("log message")
.setAttribute(WIDGET_SHAPE, "square")
.setSeverity(Severity.INFO)
.emit();
}
}
Semantic attributes
The semantic conventions describe how to collect telemetry in a standardized way for common operations. This includes an attribute registry, which enumerates definitions for all attributes referenced in the conventions, organized by domain. The semantic-conventions-java project generates constants from the semantic conventions, which can be used to help instrumentation conform:
Description | Artifact |
---|---|
Generated code for stable semantic conventions | io.opentelemetry.semconv:opentelemetry-semconv:1.28.0-alpha |
Generated code for incubating semantic conventions | io.opentelemetry.semconv:opentelemetry-semconv-incubating:1.28.0-alpha |
opentelemetry-semconv
and
opentelemetry-semconv-incubating
include the -alpha
suffix and are subject
to breaking changes, the intent is to stabilize opentelemetry-semconv
and
leave the -alpha
suffix on opentelemetry-semconv-incubating
permanently.
Libraries can use opentelemetry-semconv-incubating
for testing, but should not
include it as a dependency: since attributes may come and go from version to
version, including it as a dependency may expose end users to runtime errors
when transitive version conflicts occur.The attribute constants generated from semantic conventions are instances of
AttributeKey<T>
, and can be used anywhere the OpenTelemetry API accepts
attributes.
The following code snippet explores semantic convention attribute API usage:
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 is a bundle of application defined key-value pairs associated with a distributed request or workflow execution. Baggage keys and values are strings, and values have optional string metadata. Telemetry can be enriched with data from baggage by configuring the SDK to add entries as attributes to spans, metrics, and log records. The baggage API is built on top of context, which allows span context to be implicitly passed around an application and across threads. Most of the context API usage guidance applies to baggage.
Baggage is propagated across application boundaries with the W3CBaggagePropagator (see TextMapPropagator for details).
The following code snippet explores Baggage
API usage:
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(", ", "{", "}"));
}
}
Incubating API
The io.opentelemetry:opentelemetry-api-incubator:1.44.1-alpha
artifact contains experimental trace, metric, log, and context APIs which.
Incubating APIs may have breaking API changes in minor releases. Often, these
represent experimental specification features or API designs we want to vet with
user feedback before committing to. We encourage users to try these APIs and
open issues with any feedback (positive or negative). Libraries should not
depend on the incubating APIs, since users may be exposed to runtime errors when
transitive version conflicts occur.
See incubator README for available APIs and sample usage.
[i18n] feedback_title
[i18n] feedback_question
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!