opentelemetry.io/content/en/docs/java/manual_instrumentation.md

387 lines
12 KiB
Markdown

---
Title: "Manual Instrumentation"
Weight: 3
---
## Traces
### Instantiate `TracerProvider`
In the [OpenTelemetry Tracing
API](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md),
the `TracerProvider` is the main entry point and is expected to be the stateful
object that holds any configuration. The `TracerProvider` provides access
to the [`Tracer`](#instantiate-tracer).
```java
TracerProvider tracerProvider =
OpenTelemetry.getGlobalTracerProvider();
```
### Instantiate `Tracer`
In order to instrument, you must acquire a `Tracer`. A `Tracer` is responsible for
creating spans. To acquire a `Tracer` use the [OpenTelemetry Tracing
API](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md)
and specify the name and version of the [library
instrumenting](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/glossary.md#instrumentation-library)
the [instrumented
library](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/glossary.md#instrumented-library)
or application to be monitored.
```java
Tracer tracer =
tracerProvider.getTracer("instrumentation-library-name","1.0.0");
```
### Create Spans
#### Basic
To create a basic span, you only need to specify the name of the span. The
start and end time of the span is automatically set by the OpenTelemetry SDK.
```java
Span span = tracer.spanBuilder("my span").startSpan();
try (Scope scope = span.makeCurrent()) {
// your use case
...
} catch (Throwable t) {
span.setStatus(StatusCode.ERROR, "Change it to your error message");
} finally {
span.end(); // closing the scope does not end the span, this has to be done manually
}
```
#### Nested
Most of the time, we want to correlate spans for nested operations.
OpenTelemetry supports tracing within processes and across remote processes.
For more information about how to share context between remote processes, see [Context
Propagation](#context-propagation).
For a method `a` calling a method `b`, the spans could be manually linked in the
following way:
```java
void a() {
Span parentSpan = tracer.spanBuilder("a")
.startSpan();
b(parentSpan);
parentSpan.end();
}
void b(Span parentSpan) {
Span childSpan = tracer.spanBuilder("b")
.setParent(Context.current().with(parentSpan))
.startSpan();
// do stuff
childSpan.end();
}
```
The OpenTelemetry API also offers an automated way to propagate the parentSpan:
```java
void a() {
Span parentSpan = tracer.spanBuilder("a").startSpan();
try(Scope scope = parentSpan.makeCurrent()) {
b();
} finally {
parentSpan.end();
}
}
void b() {
Span childSpan = tracer.spanBuilder("b")
// NOTE: setting the parent Context explicitly is not required;
// The span in the Context on the current thread is automatically added as parent
.startSpan();
try(Scope scope = childSpan.makeCurrent()) {
// do stuff
} finally {
childSpan.end();
}
}
```
To link spans from remote processes, it is sufficient to set the [Remote
Context](#context-propagation) as parent.
```java
Span childRemoteParent = tracer.spanBuilder("Child").setParent(remoteContext).startSpan();
```
### Enrich Spans
#### Attributes
In OpenTelemetry, you can create spans freely. It's up to the implementor to
annotate spans with attributes specific to the represented operation. Attributes
provide additional context on a span about the specific operation it tracks,
such as results or operation properties.
```java
Span span = tracer.spanBuilder("/resource/path").setSpanKind(Span.Kind.CLIENT).startSpan();
span.setAttribute("http.method", "GET");
span.setAttribute("http.url", url.toString());
```
Some of these operations represent calls that use well-known protocols like
HTTP or database calls. For these operations, OpenTelemetry requires specific attributes
to be set. The full attribute list is available in the Semantic Conventions in
the cross-language specification. And, the standard attribute keys are available in the
`io.opentelemetry.api.trace.attributes.SemanticAttributes` class as constants.
#### Events
Spans can be annotated with named events that can carry zero or more [Span
Attributes](#attributes), each of which is itself a name/value map paired
automatically with a timestamp.
```java
span.addEvent("Init");
...
span.addEvent("End");
```
```java
Attributes eventAttributes = Attributes.of(
AttributeKey.stringKey("key"), "value",
AttributeKey.longKey("result"), 0L);
span.addEvent("End Computation", eventAttributes);
```
#### Links
You can link a span to zero or more other spans that are causally related.
Use links to represent batched operations where a span was initiated by
multiple initiating spans, each representing a single incoming item being
processed in the batch.
```java
Span child = tracer.spanBuilder("childWithLink")
.addLink(parentSpan1.getSpanContext())
.addLink(parentSpan2.getSpanContext())
.addLink(parentSpan3.getSpanContext())
.addLink(remoteContext)
.startSpan();
```
For more information about how to read context from remote processes, see [Context
Propagation](#context-propagation).
### Context Propagation
OpenTelemetry provides a text-based approach to propagate context to remote
services. By default, the [W3C Trace Context](https://www.w3.org/TR/trace-context/)
format is used.
The following example shows an outgoing HTTP request using
`HttpURLConnection`:
```java
// Tell OpenTelemetry to inject the context in the HTTP headers
TextMapPropagator.Setter<HttpURLConnection> setter =
new TextMapPropagator.Setter<HttpURLConnection>() {
@Override
public void put(HttpURLConnection carrier, String key, String value) {
// Insert the context as Header
carrier.setRequestProperty(key, value);
}
};
URL url = new URL("http://127.0.0.1:8080/resource");
Span outGoing = tracer.spanBuilder("/resource").setSpanKind(Span.Kind.CLIENT).startSpan();
try (Scope scope = outGoing.makeCurrent()) {
// Semantic Convention.
// (Observe that to set these, the Span does not *need* to be the current instance in Context or Scope.)
outGoing.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
outGoing.setAttribute(SemanticAttributes.HTTP_URL, url.toString());
HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();
// Inject the request with the *current* Context, which contains our current Span.
OpenTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), transportLayer, setter);
// Make outgoing call
} finally {
outGoing.end();
}
...
```
Similarly, you can use the text-based approach to read the W3C Trace Context
from incoming requests. The following example demonstrates processing an
incoming HTTP request using
[HttpExchange](https://docs.oracle.com/javase/8/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/HttpExchange.html):
```java
TextMapPropagator.Getter<HttpExchange> getter =
new TextMapPropagator.Getter<HttpExchange>() {
@Override
public String get(HttpExchange carrier, String key) {
if (carrier.getRequestHeaders().containsKey(key)) {
return carrier.getRequestHeaders().get(key).get(0);
}
return null;
}
@Override
public Iterable<String> keys(HttpExchange carrier) {
return carrier.getRequestHeaders().keySet();
}
};
...
public void handle(HttpExchange httpExchange) {
// Extract the SpanContext and other elements from the request.
Context extractedContext = OpenTelemetry.getPropagators().getTextMapPropagator()
.extract(Context.current(), httpExchange, getter);
Span serverSpan = null;
try (Scope scope = extractedContext.makeCurrent()) {
// Automatically use the extracted SpanContext as parent.
serverSpan = tracer.spanBuilder("GET /resource").setSpanKind(Span.Kind.SERVER)
.startSpan();
try {
// Add the attributes defined in the Semantic Conventions
serverSpan.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
serverSpan.setAttribute(SemanticAttributes.HTTP_SCHEME, "http");
serverSpan.setAttribute(SemanticAttributes.HTTP_HOST, "localhost:8080");
serverSpan.setAttribute(SemanticAttributes.HTTP_TARGET, "/resource");
// Serve the request
...
} finally {
serverSpan.end();
}
}
}
```
Other propagators are available as extensions, most notably
[Zipkin
B3](https://github.com/open-telemetry/opentelemetry-java/tree/master/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation).
### Configuring the default OpenTelemetry SDK
In order to configure the default OpenTelemetry SDK, you need to get a handle to a
`TracerManagement` instance from the SDK:
```java
TracerSdkManagement tracerManagement = OpenTelemetrySdk.getGlobalTracerManagement();
```
### Processors
The following processors are available today:
#### SimpleSpanProcessor
This span processor exports spans immediately after they end.
Example:
```java
SimpleSpanProcessor simpleSpansProcessor = SimpleSpanProcessor.builder(exporter).build();
tracerManagement.addSpanProcessor(simpleSpansProcessor);
```
#### BatchSpanProcessor
This span processor exports spans in batches.
Example:
```java
BatchSpanProcessor batchSpansProcessor =
BatchSpanProcessor.builder(exporter).build();
tracerManagement.addSpanProcessor(batchSpansProcessor);
```
You can also specify a variety of configuration parameters:
```java
BatchSpanProcessor batchSpansProcessor =
BatchSpanProcessor.builder(exporter)
.setExportOnlySampled(true) // send only sampled spans to the exporter
.setMaxExportBatchSize(512) // maximum batch size to use
.setMaxQueueSize(2048) // queue size; mmust be >= the export batch size
.setExporterTimeoutMillis(
30_000) // max amount of time an export can run before getting interrupted
.setScheduleDelayMillis(5000) // set time between two different exports
.build();
tracerManagement.addSpanProcessor(batchSpansProcessor);
```
#### MultiSpanProcessor
A MultiSpanProcessor accepts a list of span processors.
Example:
```java
SpanProcessor multiSpanProcessor =
MultiSpanProcessor.create(Arrays.asList(simpleSpansProcessor, batchSpansProcessor));
tracerManagement.addSpanProcessor(multiSpanProcessor);
```
### Exporters
*TODO*
### Sampling
*TODO*
## Metrics
OpenTelemetry provides support for metrics, a time series of numbers that might
express things such as CPU utilization, request count for an HTTP server, or a
business metric such as transactions.
All metrics can be annotated with labels. Labels are additional qualifiers that help
describe what subdivision of the measurements the metric represents.
The following is an example of counter usage:
```java
// Gets or creates a named meter instance
Meter meter = OpenTelemetry.getGlobalMeter("instrumentation-library-name","semver:1.0.0");
// Build counter e.g. LongCounter
LongCounter counter = meter
.longCounterBuilder("processed_jobs")
.setDescription("Processed jobs")
.setUnit("1")
.build();
// It is recommended that the API user keep a reference to a Bound Counter for the entire time or
// call unbind when no-longer needed.
BoundLongCounter someWorkCounter = counter.bind(Labels.of("Key", "SomeWork"));
// Record data
someWorkCounter.add(123);
// Alternatively, the user can use the unbounded counter and explicitly
// specify the labels set at call-time:
counter.add(123, Labels.of("Key", "SomeWork"));
```
`Observer` is an additional instrument supporting an asynchronous API and
collecting metric data on demand, once per collection interval.
The following is an example of observer usage:
```java
// Build observer e.g. LongObserver
LongObserver observer = meter
.observerLongBuilder("cpu_usage")
.setDescription("CPU Usage")
.setUnit("ms")
.build();
observer.setCallback(
new LongObserver.Callback<LongObserver.ResultLongObserver>() {
@Override
public void update(ResultLongObserver result) {
// long getCpuUsage()
result.observe(getCpuUsage(), Labels.of("Key", "SomeWork"));
}
});
```