Manually instrument Java applications with OpenTelemetry
This guide shows how to manually instrument your Java application with OpenTelemetry and export the traces to Dynatrace.
- To learn more about how Dynatrace works with OpenTelemetry, see Send data to Dynatrace with OpenTelemetry.
- To learn about automatic instrumentation, see Automatically instrument Java applications with OpenTelemetry.
- To learn how to export the metrics to Dynatrace with OpenTelemetry Instrument Java applications with OpenTelemetry Metrics.
Prerequisites
- Dynatrace version 1.222+
- W3C Trace Context is enabled
- From the Dynatrace menu, go to Settings > Server-side service monitoring > Deep monitoring > Distributed tracing.
- Turn on Send W3C Trace Context HTTP headers.
Overview
To monitor your Java application with OpenTelemetry, you need to
Instrument your application
Send the data to Dynatrace
Configure context propagation
Verify that the traces are ingested into Dynatrace
Configure data capture to meet privacy requirements
Instrument your application
To instrument manually, add the following dependencies to your project, and add the code snippet below to any Java method you want to monitor.
Set names for the tracer, the span, and add attributes as you see fit.
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
</dependencies>
dependencies {
implementation("io.opentelemetry:opentelemetry-api")
}
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
Tracer tracer = GlobalOpenTelemetry
.getTracerProvider()
.tracerBuilder("my-tracer") //TODO Replace with the name of your tracer
.build();
Span span = tracer
.spanBuilder("my-span") //TODO Replace with the name of your span
.setAttribute("my-key-1", "my-value-1") //TODO Add initial attributes
.startSpan();
try (Scope scope = span.makeCurrent()) {
span.setAttribute("key-2", "value-2"); //TODO Add extra attributes if necessary
//TODO your code goes here
} finally {
span.end();
}
Send data to Dynatrace
You have three options for sending data to Dynatrace.
To send data to Dynatrace, you need to configure your environment variables as follows:
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
OTEL_METRICS_EXPORTER=none
OTEL_TRACES_EXPORTER=none
Exporters are set to none
, as Dynatrace will automatically ingest traces and metrics. If these environment variables were not set, OpenTelemetry would default to localhost
, which would throw errors if there was no service listening on the default ports.
To send data to Dynatrace, you have to add the following extra dependencies, the code snippet below in your application startup code, as well as configure the required environment variables.
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
<version>1.12.0-alpha</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp-http-trace</artifactId>
<version>1.12.0</version>
</dependency>
</dependencies>
dependencies {
implementation 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.12.0-alpha'
implementation 'io.opentelemetry:opentelemetry-exporter-otlp-http-trace:1.12.0'
}
Add the following imports to your code and create a method to enrich the OpenTelemetry Resource with Dynatrace-specific metadata:
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
private static void initOpenTelemetry() {
AutoConfiguredOpenTelemetrySdk.builder().addResourceCustomizer((resource, properties) -> {
Resource dtMetadata = Resource.empty();
for (String name : new String[]{"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", "/var/lib/dynatrace/enrichment/dt_metadata.properties"}) {
try {
Properties props = new Properties();
props.load(name.startsWith("/var") ? new FileInputStream(name) : new FileInputStream(Files.readAllLines(Paths.get(name)).get(0)));
dtMetadata = dtMetadata.merge(Resource.create(props.entrySet().stream()
.collect(Attributes::builder, (b, e) -> b.put(e.getKey().toString(), e.getValue().toString()), (b1, b2) -> b1.putAll(b2.build()))
.build())
);
} catch (IOException e) {
}
}
return resource.merge(dtMetadata);
}).build().getOpenTelemetrySdk();
}
Make sure to call the created method as soon as your app starts.
Configure your environment variables as follows:
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=<URL>
OTEL_EXPORTER_OTLP_TRACES_HEADERS=Authorization="Api-Token <TOKEN>"
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
OTEL_RESOURCE_ATTRIBUTES="service.name=java-quickstart,service.version=1.0.1"
Replace the service name and version attributes with the name and version of your application.
Lastly, you need to define the correct endpoint and token, to make sure your data arrives where it should be.
- To set the endpoint:
- Use your Environment ID to set the endpoint to which your app will send traces as follows:
- Dynatrace SaaS
https://{your-environment-id}.live.dynatrace.com/api/v2/otlp/v1/traces
- Dynatrace Managed
https://{your-domain}/e/{your-environment-id}/api/v2/otlp/v1/traces
- Dynatrace ActiveGate
https://{your-activegate-endpoint}/e/{your-environment-id}/api/v2/otlp/v1/traces
- You may need to include the port to your ActiveGate endpoint. For example:
https://{your-activegate-endpoint}:9999/e/{your-environment-id}/api/v2/otlp/v1/traces
- If you are running a containerized ActiveGate, you need to use the FQDN of it. For example:
https://{your-activegate-service-name}.dynatrace.svc.cluster.local/e/{your-environment-id}/api/v2/otlp/v1/traces
- You may need to include the port to your ActiveGate endpoint. For example:
- Dynatrace SaaS
- Replace
<URL>
in the code snippet above with your endpoint.
- Use your Environment ID to set the endpoint to which your app will send traces as follows:
- To create an authentication token
- In the Dynatrace menu, go to Access tokens and select Generate new token.
- Provide a Token name.
- In the Search scopes box, search for
Ingest OpenTelemetry traces
and select the checkbox. - Select Generate token.
- Select Copy to copy the token to your clipboard.
- Save the token in a safe place; you can't display it again.
- Replace
<TOKEN>
in the code snippet above with your token.
To send data to Dynatrace, you have to add the dependencies as well as add and configure the code snippet below in your Java application code.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.12.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp-http-trace</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-semconv</artifactId>
<version>1.12.0-alpha</version>
</dependency>
</dependencies>
dependencies {
implementation(platform("io.opentelemetry:opentelemetry-bom:1.12.0"))
implementation("io.opentelemetry:opentelemetry-api")
implementation("io.opentelemetry:opentelemetry-sdk")
implementation("io.opentelemetry:opentelemetry-exporter-otlp-http-trace")
implementation("io.opentelemetry:opentelemetry-semconv:1.12.0-alpha")
}
Add the following imports to your code and create a method to configure the OpenTelemetry Global Tracer Provider, as well as enrich the OpenTelemetry Resource with Dynatrace-specific metadata:
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
private static void initOpenTelemetry() {
Resource serviceName = Optional.ofNullable(System.getenv("OTEL_SERVICE_NAME"))
.map(n -> Attributes.of(AttributeKey.stringKey("service.name"), n))
.map(Resource::create)
.orElseGet(Resource::empty);
Resource envResourceAttributes = Resource.create(Stream.of(Optional.ofNullable(System.getenv("OTEL_RESOURCE_ATTRIBUTES")).orElse("").split(","))
.filter(pair -> pair != null && pair.length() > 0 && pair.contains("="))
.map(pair -> pair.split("="))
.filter(pair -> pair.length == 2)
.collect(Attributes::builder, (b, p) -> b.put(p[0], p[1]), (b1, b2) -> b1.putAll(b2.build()))
.build()
);
Resource dtMetadata = Resource.empty();
for (String name : new String[] {"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", "/var/lib/dynatrace/enrichment/dt_metadata.properties"}) {
try {
Properties props = new Properties();
props.load(name.startsWith("/var") ? new FileInputStream(name) : new FileInputStream(Files.readAllLines(Paths.get(name)).get(0)));
dtMetadata = dtMetadata.merge(Resource.create(props.entrySet().stream()
.collect(Attributes::builder, (b, e) -> b.put(e.getKey().toString(), e.getValue().toString()), (b1, b2) -> b1.putAll(b2.build()))
.build())
);
} catch (IOException e) {}
}
SpanExporter exporter = OtlpHttpSpanExporter.builder()
.setEndpoint("<URL>") //TODO Replace <URL> to your SaaS/Managed-URL as mentioned in the next step
.addHeader("Authorization", "Api-Token <TOKEN>") //TODO Replace <TOKEN> with your API Token as mentioned in the next step
.build();
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
.setResource(Resource.getDefault().merge(envResourceAttributes).merge(serviceName).merge(dtMetadata))
.setSampler(Sampler.alwaysOn())
.addSpanProcessor(BatchSpanProcessor.builder(exporter).build())
.build();
OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.buildAndRegisterGlobal();
Runtime.getRuntime().addShutdownHook(new Thread(sdkTracerProvider::close));
}
Make sure to call the created method as soon as your app starts.
Configure your environment variables as follows:
OTEL_RESOURCE_ATTRIBUTES="service.name=java-quickstart,service.version=1.0.1"
Alternatively:
OTEL_SERVICE_NAME="java-quickstart"
Replace the service name and version attributes with the name and version of your application.
Lastly, you need to define the correct endpoint and token, to make sure your data arrives where it should be.
- To set the endpoint:
- Use your Environment ID to set the endpoint to which your app will send traces as follows:
- Dynatrace SaaS
https://{your-environment-id}.live.dynatrace.com/api/v2/otlp/v1/traces
- Dynatrace Managed
https://{your-domain}/e/{your-environment-id}/api/v2/otlp/v1/traces
- Dynatrace ActiveGate
https://{your-activegate-endpoint}/e/{your-environment-id}/api/v2/otlp/v1/traces
- You may need to include the port to your ActiveGate endpoint. For example:
https://{your-activegate-endpoint}:9999/e/{your-environment-id}/api/v2/otlp/v1/traces
- If you are running a containerized ActiveGate, you need to use the FQDN of it. For example:
https://{your-activegate-service-name}.dynatrace.svc.cluster.local/e/{your-environment-id}/api/v2/otlp/v1/traces
- You may need to include the port to your ActiveGate endpoint. For example:
- Dynatrace SaaS
- Replace
<URL>
in the code snippet above with your endpoint.
- Use your Environment ID to set the endpoint to which your app will send traces as follows:
- To create an authentication token
- In the Dynatrace menu, go to Access tokens and select Generate new token.
- Provide a Token name.
- In the Search scopes box, search for
Ingest OpenTelemetry traces
and select the checkbox. - Select Generate token.
- Select Copy to copy the token to your clipboard.
- Save the token in a safe place; you can't display it again.
- Replace
<TOKEN>
in the code snippet above with your token.
Configure context propagation optional
If you use manual instrumentation or a framework that is not supported by OpenTelemetry, you need to configure context propagation. If your application receives a request or calls other applications, you need to configure context propagation to make sure the spans are linked together.
- Whenever receiving an incoming request, you need to extract the parent context and create the new span as a child of it.
//The getter will be used for incoming requests
TextMapGetter<HttpExchange> getter =
new TextMapGetter<>() {
@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);
try (Scope scope = extractedContext.makeCurrent()) {
//This will automatically propagate context by creating child spans within the extracted context
Span serverSpan = tracer.spanBuilder("my-server-span") //TODO Replace with the name of your span
.setSpanKind(SpanKind.SERVER) //TODO Set the kind of your span
.startSpan();
serverSpan.setAttribute(SemanticAttributes.HTTP_METHOD, "GET"); //TODO Add attributes
serverSpan.end();
}
}
- If your application calls another service, you need to ensure that you propagate the context, adding it to your outgoing request.
//The setter will be used for outgoing requests
TextMapSetter<HttpURLConnection> setter =
(carrier, key, value) -> {
assert carrier != null;
// Insert the context as Header
carrier.setRequestProperty(key, value);
};
URL url = new URL("<URL>"); //TODO Replace with the URL of the service to be called
Span outGoing = tracer.spanBuilder("my-client-span") //TODO Replace with the name of your span
.setSpanKind(SpanKind.CLIENT) //TODO Set the kind of your span
.startSpan();
try (Scope scope = outGoing.makeCurrent()) {
outGoing.setAttribute(SemanticAttributes.HTTP_METHOD, "GET"); //TODO Add attributes
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();
}
Verify that the traces are ingested into Dynatrace
A few minutes after restarting your app, look for your spans:
- In the Dynatrace menu, go to Distributed traces and select the Ingested traces tab.
- If you use OneAgent, go to Distributed traces and select the PurePaths tab.
- Your spans will be part of an existing PurePath, if the root of your call is already monitored by OneAgent.
If your application does not receive any traffic, there will be no traces.
Configure data capture to meet privacy requirements optional
While Dynatrace automatically captures all OpenTelemetry resource and span attributes, only attribute values specified in the allowlist are stored and displayed in the Dynatrace web UI. This prevents accidental storage of personal data, so you can meet your privacy requirements and control the amount of monitoring data that's stored.
To view your custom span attributes, you need to allow them in the Dynatrace web UI first:
- Span attributes In the Dynatrace menu, go to Settings and select Server-side service monitoring > Span attributes.
- Resource attributes In the Dynatrace menu, go to Settings and select Server-side service monitoring > Resource attributes.