@Singleton
class HelloService {
@NewSpan("hello-world") (1)
public String hello(@SpanTag("person.name") String name) { (2)
return greet("Hello " + name);
}
@ContinueSpan (3)
public String greet(@SpanTag("hello.greeting") String greet) {
return greet;
}
}
Table of Contents
Micronaut Tracing
Adds Distributed Tracing Support
Version: 8.0.0-SNAPSHOT
1 Introduction
When operating Microservices in production it can be challenging to troubleshoot interactions between Microservices in a distributed architecture.
To solve this problem, a way to visualize interactions between Microservices in a distributed manner can be critical. Currently, there are various distributed tracing solutions, the most popular of which are Zipkin and Jaeger, both of which provide different levels of support for the Open Tracing API.
Micronaut features integration with both Zipkin and Jaeger (via the Open Tracing API).
Tracing Annotations
The io.micronaut.tracing.annotation package contains annotations for creating new spans, continuing existing spans, and adding span tags. You can also declare @NewSpan on a class or interface to create a new span for each method invocation.
The available annotations are:
-
The @NewSpan annotation creates a new span, wrapping the method call or reactive type. It can be declared on a method, class, or interface. Method-level annotations take precedence over class-level or interface-level annotations.
-
The @ContinueSpan annotation continues an existing span, wrapping the method call or reactive type.
-
The @SpanTag annotation can be used on method arguments to include the value of the argument within a Span’s tags. When you use
@SpanTagon an argument, you must either annotate the method with@NewSpanor@ContinueSpan.
The following snippet presents an example of using the annotations:
| 1 | The @NewSpan annotation starts a new span |
| 2 | Use @SpanTag to include method arguments as tags for the span |
| 3 | Use the @ContinueSpan annotation to continue an existing span and incorporate additional tags using @SpanTag |
Tracing Instrumentation
In addition to explicit tracing tags, Micronaut includes a number of instrumentations to ensure that the Span context is propagated between threads and across Microservice boundaries.
These instrumentations are found in the io.micronaut.tracing.instrument package and include Client Filters and Server Filters to propagate the necessary headers via HTTP.
Tracing Beans
If the Tracing annotations and existing instrumentations are not sufficient, Micronaut’s tracing integration registers a io.opentracing.Tracer bean which exposes the Open Tracing API and can be dependency-injected as needed.
Depending on the implementation you choose, there are also additional beans. For example for Zipkin brave.Tracing and brave.SpanCustomizer beans are available too.
2 Release History
For this project, you can find a list of releases (with release notes) here:
3 Breaking Changes
This section will document breaking changes that may happen during milestone or release candidate releases, as well as major releases eg (1.x.x → 2.x.x).
Micronaut Tracing 4.5.0 breaking changes
Micronaut Tracing 4.5.0 upgrades the OpenTelemetry annotation support to OpenTelemetry Instrumentation 1.19.0.
That release moved @WithSpan and @SpanAttribute from io.opentelemetry.extension.annotations to io.opentelemetry.instrumentation.annotations.
If your application uses micronaut-tracing-opentelemetry-annotation and imports the old package, update those imports to the new package before upgrading to Micronaut Tracing 4.5.0 or newer.
If you upgrade to Micronaut Tracing 4.5.0 independently from the Micronaut Platform version used by your application, import the matching io.micronaut.tracing:micronaut-tracing-bom version (the same version as your Micronaut Tracing version) in dependency management.
Applications that keep an older Micronaut Platform-managed tracing BOM while forcing only the Micronaut Tracing OpenTelemetry module version can resolve incompatible OpenTelemetry instrumentation, semantic conventions, SDK, and exporter artifacts.
Micronaut Tracing 5.0.0-M2 breaking changes
The Micronaut Tracing Zipkin module (io.micronaut.tracing:micronaut-tracing-zipkin) has been renamed and separated in two new modules:
-
Micronaut Tracing Brave (
io.micronaut.tracing:micronaut-tracing-brave) -
Micronaut Tracing Brave HTTP (
io.micronaut.tracing:micronaut-tracing-brave-http)
if you are using OpenTracing and Micronaut Tracing Zipkin module, you have to change io.micronaut.tracing:micronaut-tracing-zipkin dependency to io.micronaut.tracing:micronaut-tracing-brave-http. The Micronaut Tracing Brave HTTP brings HTTP filters for auto instrumentation of your requests. If you don’t need HTTP filters you can only add io.micronaut.tracing:micronaut-tracing-brave dependency.
Micronaut Tracing 7.0.0 Breaking changes
The following dependencies have been updated:
-
Open Telemetry 1.36
-
Open Telemetry Instrumentation 1.33.1
The Open Telemetry updates required updating the following Zipkin dependencies to a new major version:
-
Zipkin Reporter 3.3.0
-
Zipkin Brave 6.0.2
The only change due to the above dependency updates that might affect end user code is that the errorParser property has been removed from io.micronaut.tracing.brave.BraveTracerConfiguration as the brave.ErrorParser type no longer exists.
The io.micronaut.tracing.zipkin.http.client.HttpClientSender class is now deprecated as it implements a deprecated Zipkin API. This class will be replaced with an implementation based on the Zipkin 3 APIs in a future release.
4 Tracing with Jaeger
Jaeger is a distributed tracing system developed at Uber that is more or less the reference implementation for Open Tracing.
Running Jaeger
The easiest way to get started with Jaeger is with Docker:
$ docker run -d \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:1.6
Navigate to http://localhost:16686 to access the Jaeger UI.
See Getting Started with Jaeger for more information.
Sending Traces to Jaeger
|
Using the CLI
If you create your project using the Micronaut CLI, supply the $ mn create-app my-app --features tracing-jaeger |
To send tracing spans to Jaeger, add the micronaut-tracing-jaeger dependency in your build:
implementation("io.micronaut.tracing:micronaut-tracing-jaeger")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-jaeger</artifactId>
</dependency>
Then enable Jaeger tracing in your configuration (potentially only your production configuration):
tracing.jaeger.enabled=true
tracing:
jaeger:
enabled: true
[tracing]
[tracing.jaeger]
enabled=true
tracing {
jaeger {
enabled = true
}
}
{
tracing {
jaeger {
enabled = true
}
}
}
{
"tracing": {
"jaeger": {
"enabled": true
}
}
}
By default, Jaeger will be configured to send traces to a locally running Jaeger agent.
Jaeger Configuration
There are many configuration options available for the Jaeger client that sends Spans to Jaeger, and they are generally exposed via the JaegerConfiguration class. Refer to the Javadoc for available options.
Below is an example of customizing JaegerConfiguration configuration:
tracing.jaeger.enabled=true
tracing.jaeger.sampler.probability=0.5
tracing.jaeger.sender.agentHost=foo
tracing.jaeger.sender.agentPort=5775
tracing.jaeger.reporter.flushInterval=2000
tracing.jaeger.reporter.maxQueueSize=200
tracing.jaeger.codecs=W3C,B3,JAEGER
tracing:
jaeger:
enabled: true
sampler:
probability: 0.5
sender:
agentHost: foo
agentPort: 5775
reporter:
flushInterval: 2000
maxQueueSize: 200
codecs: W3C,B3,JAEGER
[tracing]
[tracing.jaeger]
enabled=true
codecs="W3C,B3,JAEGER"
[tracing.jaeger.sampler]
probability=0.5
[tracing.jaeger.sender]
agentHost="foo"
agentPort=5775
[tracing.jaeger.reporter]
flushInterval=2000
maxQueueSize=200
tracing {
jaeger {
enabled = true
sampler {
probability = 0.5
}
sender {
agentHost = "foo"
agentPort = 5775
}
reporter {
flushInterval = 2000
maxQueueSize = 200
}
codecs = "W3C,B3,JAEGER"
}
}
{
tracing {
jaeger {
enabled = true
sampler {
probability = 0.5
}
sender {
agentHost = "foo"
agentPort = 5775
}
reporter {
flushInterval = 2000
maxQueueSize = 200
}
codecs = "W3C,B3,JAEGER"
}
}
}
{
"tracing": {
"jaeger": {
"enabled": true,
"sampler": {
"probability": 0.5
},
"sender": {
"agentHost": "foo",
"agentPort": 5775
},
"reporter": {
"flushInterval": 2000,
"maxQueueSize": 200
},
"codecs": "W3C,B3,JAEGER"
}
}
}
You can also optionally dependency-inject common configuration classes into JaegerConfiguration such as io.jaegertracing.Configuration.SamplerConfiguration just by defining them as beans. Likewise, a custom io.opentracing.ScopeManager can be injected into JaegerTracerFactory. See the API for JaegerConfiguration and JaegerTracerFactory for available injection points.
Filtering HTTP spans
It may be useful to exclude health-checks and other HTTP requests to your service. This can be achieved by adding a list of regular expression patterns to your configuration:
tracing.jaeger.enabled=true
tracing.exclusions[0]=/health
tracing.exclusions[1]=/env/.*
tracing:
jaeger:
enabled: true
exclusions:
- /health
- /env/.*
[tracing]
exclusions=[
"/health",
"/env/.*"
]
[tracing.jaeger]
enabled=true
tracing {
jaeger {
enabled = true
}
exclusions = ["/health", "/env/.*"]
}
{
tracing {
jaeger {
enabled = true
}
exclusions = ["/health", "/env/.*"]
}
}
{
"tracing": {
"jaeger": {
"enabled": true
},
"exclusions": ["/health", "/env/.*"]
}
}
Jaeger Guides
See the following guides to learn more about distributed tracing with Jaeger in the Micronaut Framework:
5 Tracing with Zipkin
Zipkin is a distributed tracing system. It helps gather timing data to troubleshoot latency problems in microservice architectures. It manages both the collection and retrieval of this data.
Running Zipkin
The quickest way to get up and started with Zipkin is with Docker:
$ docker run -d -p 9411:9411 openzipkin/zipkin
Navigate to http://localhost:9411 to view traces.
Sending Traces to Zipkin
|
Using the CLI
If you create your project using the Micronaut CLI, supply the $ mn create-app my-app --features tracing-zipkin |
To send tracing spans to Zipkin, add the micronaut-tracing-brave-http dependency in your build:
implementation("io.micronaut.tracing:micronaut-tracing-brave-http")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-brave-http</artifactId>
</dependency>
Then enable ZipKin tracing in your configuration (potentially only your production configuration):
tracing.zipkin.enabled=true
tracing:
zipkin:
enabled: true
[tracing]
[tracing.zipkin]
enabled=true
tracing {
zipkin {
enabled = true
}
}
{
tracing {
zipkin {
enabled = true
}
}
}
{
"tracing": {
"zipkin": {
"enabled": true
}
}
}
Customizing the Zipkin Sender
To send spans you configure a Zipkin sender. You can configure a HttpClientSender that sends Spans asynchronously using Micronaut’s native HTTP client with the tracing.zipkin.http.url setting:
tracing.zipkin.enabled=true
tracing.zipkin.http.url=http://localhost:9411
tracing:
zipkin:
enabled: true
http:
url: http://localhost:9411
[tracing]
[tracing.zipkin]
enabled=true
[tracing.zipkin.http]
url="http://localhost:9411"
tracing {
zipkin {
enabled = true
http {
url = "http://localhost:9411"
}
}
}
{
tracing {
zipkin {
enabled = true
http {
url = "http://localhost:9411"
}
}
}
}
{
"tracing": {
"zipkin": {
"enabled": true,
"http": {
"url": "http://localhost:9411"
}
}
}
}
It is unlikely that sending spans to localhost will be suitable for production deployment, so you generally need to configure the location of one or more Zipkin servers for production:
tracing.zipkin.enabled=true
tracing.zipkin.http.urls[0]=https://foo:9411
tracing.zipkin.http.urls[1]=https://bar:9411
tracing:
zipkin:
enabled: true
http:
urls:
- https://foo:9411
- https://bar:9411
[tracing]
[tracing.zipkin]
enabled=true
[tracing.zipkin.http]
urls=[
"https://foo:9411",
"https://bar:9411"
]
tracing {
zipkin {
enabled = true
http {
urls = ["https://foo:9411", "https://bar:9411"]
}
}
}
{
tracing {
zipkin {
enabled = true
http {
urls = ["https://foo:9411", "https://bar:9411"]
}
}
}
}
{
"tracing": {
"zipkin": {
"enabled": true,
"http": {
"urls": ["https://foo:9411", "https://bar:9411"]
}
}
}
}
In production, setting TRACING_ZIPKIN_HTTP_URLS environment variable with a comma-separated list of URLs also works.
|
Alternatively, to use a different zipkin2.reporter.Sender implementation, you can define a bean of type zipkin2.reporter.Sender and it will be used instead.
Zipkin Configuration
There are many configuration options available for the Brave client that sends Spans to Zipkin, and they are generally exposed via the BraveTracerConfiguration class. Refer to the Javadoc for available options.
Below is an example of customizing Zipkin configuration:
tracing.zipkin.enabled=true
tracing.zipkin.traceId128Bit=true
tracing.zipkin.sampler.probability=1
tracing:
zipkin:
enabled: true
traceId128Bit: true
sampler:
probability: 1
[tracing]
[tracing.zipkin]
enabled=true
traceId128Bit=true
[tracing.zipkin.sampler]
probability=1
tracing {
zipkin {
enabled = true
traceId128Bit = true
sampler {
probability = 1
}
}
}
{
tracing {
zipkin {
enabled = true
traceId128Bit = true
sampler {
probability = 1
}
}
}
}
{
"tracing": {
"zipkin": {
"enabled": true,
"traceId128Bit": true,
"sampler": {
"probability": 1
}
}
}
}
You can also optionally dependency-inject common configuration classes into BraveTracerConfiguration such as brave.sampler.Sampler just by defining them as beans. See the API for BraveTracerConfiguration for available injection points.
Filtering HTTP spans
It may be useful to exclude health-checks and other HTTP requests to your service. This can be achieved by adding a list of regular expression patterns to your configuration:
tracing.zipkin.enabled=true
tracing.exclusions[0]=/health
tracing.exclusions[1]=/env/.*
tracing:
zipkin:
enabled: true
exclusions:
- /health
- /env/.*
[tracing]
exclusions=[
"/health",
"/env/.*"
]
[tracing.zipkin]
enabled=true
tracing {
zipkin {
enabled = true
}
exclusions = ["/health", "/env/.*"]
}
{
tracing {
zipkin {
enabled = true
}
exclusions = ["/health", "/env/.*"]
}
}
{
"tracing": {
"zipkin": {
"enabled": true
},
"exclusions": ["/health", "/env/.*"]
}
}
Zipkin Guides
See the following guides to learn more about distributed tracing with Zipkin in the Micronaut Framework:
6 Tracing with OpenTelemetry
The Micronaut Open Telemetry module uses Open Telemetry Autoconfigure SDK to configure Open Telemetry for tracing. For some functionalities you have to add additional dependencies. The Default values that are defined inside the Micronaut which values might be different from the default ones inside Open Telemetry Autoconfigure SDK module are:
-
otel.traces.exporter = none
-
otel.metrics.exporter = none
-
otel.logs.exporter = none
-
otel.service.name = value of the application.name
OpenTelemetry libraries must be resolved using the same BOM or platform dependency management as the Micronaut Tracing OpenTelemetry modules.
If an application overrides the Micronaut Tracing version supplied by the Micronaut Platform BOM, also import the matching io.micronaut.tracing:micronaut-tracing-bom version.
This keeps OpenTelemetry instrumentation, semantic conventions, SDK, and exporter dependencies aligned.
Standard OpenTelemetry resource configuration is also supported. For example, you can set otel.resource.attributes=deployment.environment=prod,service.namespace=payments or use the equivalent OTEL_RESOURCE_ATTRIBUTES environment variable.
OpenTelemetry annotations
The io.micronaut.tracing.opentelemetry.processing package contains transformers and mappers that enables usage of Open Telemetry annotations.
6.1 OpenTelemetry Annotations
The io.micronaut.tracing.opentelemetry.processing package contains transformers and mappers that enables usage of Open Telemetry annotations.
Since Micronaut Tracing 4.5.0, OpenTelemetry’s @WithSpan and @SpanAttribute annotations must be imported from io.opentelemetry.instrumentation.annotations. Older applications that still import io.opentelemetry.extension.annotations need to update those imports when upgrading.
|
To enable Open telemetry annotations you have to add next annotation processor in your dependency block:
annotationProcessor("io.micronaut.tracing:micronaut-tracing-opentelemetry-annotation:8.0.0-SNAPSHOT")
<annotationProcessorPaths>
<path>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-opentelemetry-annotation</artifactId>
<version>8.0.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
The Open Tracing annotations that are defined inside the io.micronaut.tracing.annotation package are also available for usage inside Open Telemetry.
6.2 OpenTelemetry Exporters
In your project you can specify exporters that you want to use. The default one is set to "none" value which means by default there are no exporter registered. The available values are defined on the Open Telemetry Autoconfigure SDK documentation.
For each exporter that you want to use you have to specify it inside configuration, and you have to add required dependency:
The versionless exporter dependencies below rely on Micronaut dependency management.
If you override the Micronaut Tracing version, import the matching io.micronaut.tracing:micronaut-tracing-bom as well so that exporters use the OpenTelemetry version expected by the tracing instrumentation.
-
OpenTelemetry Protocol exporter:
otlpimplementation("io.opentelemetry:opentelemetry-exporter-otlp")<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-otlp</artifactId> </dependency> -
Logging exporter:
loggingimplementation("io.opentelemetry:opentelemetry-exporter-logging")<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-logging</artifactId> </dependency> -
Jaeger exporter:
jaegerimplementation("io.opentelemetry:opentelemetry-exporter-jaeger")<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-jaeger</artifactId> </dependency> -
Google Cloud Trace:
google_cloud_traceimplementation("com.google.cloud.opentelemetry:exporter-auto")<dependency> <groupId>com.google.cloud.opentelemetry</groupId> <artifactId>exporter-auto</artifactId> </dependency> -
Zipkin exporter:
zipkinimplementation("io.opentelemetry:opentelemetry-exporter-zipkin")<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-zipkin</artifactId> </dependency>
For Logback integration details, see the Logback section in this guide.
Example configuration for the Zipkin exporter:
otel.traces.exporter=zipkin
otel:
traces:
exporter: zipkin
[otel]
[otel.traces]
exporter="zipkin"
otel {
traces {
exporter = "zipkin"
}
}
{
otel {
traces {
exporter = "zipkin"
}
}
}
{
"otel": {
"traces": {
"exporter": "zipkin"
}
}
}
Micronaut provides Zipkin exporter that will use Micronaut’s HTTP client instead of OKHttp client. That will reduce dependency graph and will make your native executable smaller. To use it add next dependency:
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry-zipkin-exporter")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-opentelemetry-zipkin-exporter</artifactId>
</dependency>
To configure Micronaut Zipkin exporter add
otel.exporter.zipkin.url=<url-to-zipkin-server>
otel:
exporter:
zipkin:
url: <url-to-zipkin-server>
[otel]
[otel.exporter]
[otel.exporter.zipkin]
url="<url-to-zipkin-server>"
otel {
exporter {
zipkin {
url = "<url-to-zipkin-server>"
}
}
}
{
otel {
exporter {
zipkin {
url = "<url-to-zipkin-server>"
}
}
}
}
{
"otel": {
"exporter": {
"zipkin": {
"url": "<url-to-zipkin-server>"
}
}
}
}
Micronaut Zipkin exporter requires otel.traces.exporter property not to be defined to avoid conflicts with Open Telemetry default implementation of zipkin exporter.
|
6.3 Logback
To export logs from Logback through OpenTelemetry, add the Logback appender dependency:
implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0")
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
</dependency>
When the appender dependency and micronaut-tracing-opentelemetry are both on the classpath, Micronaut automatically installs the application OpenTelemetry bean into the Logback appender. No custom bootstrap code is required.
Example configuration:
otel.logs.exporter=otlp
otel:
logs:
exporter: otlp
[otel]
[otel.logs]
exporter="otlp"
otel {
logs {
exporter = "otlp"
}
}
{
otel {
logs {
exporter = "otlp"
}
}
}
{
"otel": {
"logs": {
"exporter": "otlp"
}
}
}
<configuration>
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"/>
<root level="INFO">
<appender-ref ref="OTEL"/>
</root>
</configuration>
If you want to keep using your existing appenders and only enrich Logback output with the active trace and span identifiers, add the MDC integration dependency instead:
runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-logback-mdc-1.0")
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-mdc-1.0</artifactId>
<scope>runtime</scope>
</dependency>
6.4 OpenTelemetry SDK Customization
You can provide a bean of type OpenTelemetryBuilderCustomizer to customize the OpenTelemetry autoconfigured SDK builder before Micronaut creates the io.opentelemetry.api.OpenTelemetry bean.
For example, a customizer can register a metric view with an explicit histogram aggregation and custom bucket boundaries:
import io.micronaut.context.annotation.Factory;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentSelector;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.View;
import jakarta.inject.Singleton;
import java.util.List;
@Factory
class OpenTelemetryBuilderCustomizerExample {
private static final List<Double> HISTOGRAM_BOUNDARIES = List.of(1.0d, 5.0d, 10.0d);
@Singleton
OpenTelemetryBuilderCustomizer histogramViewCustomizer() {
return builder -> builder.addMeterProviderCustomizer((meterProviderBuilder, configProperties) -> {
meterProviderBuilder.registerView(
InstrumentSelector.builder()
.setType(InstrumentType.HISTOGRAM)
.setName("http.server.request.duration")
.build(),
View.builder()
.setAggregation(Aggregation.explicitBucketHistogram(HISTOGRAM_BOUNDARIES))
.build()
);
return meterProviderBuilder;
});
}
}
The customizer receives the same AutoConfiguredOpenTelemetrySdkBuilder that Micronaut uses internally, so it can also use the OpenTelemetry SDK autoconfigure extension points such as tracer provider customizers, meter provider customizers, log record processor customizers, and property suppliers.
6.5 OpenTelemetry Propagators
In your project you can specify propagators that you want to use. The default one is set to "tracecontext, baggage". The available values are defined on the Open Telemetry Autoconfigure SDK documentation.
To use AWS X-Ray propagator inside your application you have to add next dependency in your project:
implementation("io.opentelemetry:opentelemetry-extension-aws")
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-extension-aws</artifactId>
</dependency>
And the "xray" has to be added inside configuration file.
otel.traces.exporter=otlp
otel.propagators=tracecontext, baggage, xray
otel:
traces:
exporter: otlp
propagators: tracecontext, baggage, xray
[otel]
propagators="tracecontext, baggage, xray"
[otel.traces]
exporter="otlp"
otel {
traces {
exporter = "otlp"
}
propagators = "tracecontext, baggage, xray"
}
{
otel {
traces {
exporter = "otlp"
}
propagators = "tracecontext, baggage, xray"
}
}
{
"otel": {
"traces": {
"exporter": "otlp"
},
"propagators": "tracecontext, baggage, xray"
}
}
6.6 ID Generator
ID Generator
Some custom vendor may require the span traceId in different format from the default one. You can provide your own bean of type IdGenerator. For an example, AWS X-Ray requires a specific format for their tracing identifiers. Add the following dependency, to register an instance of AwsXrayIdGenerator as a bean of type IdGenerator.
implementation("io.opentelemetry.contrib:opentelemetry-aws-xray")
<dependency>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>opentelemetry-aws-xray</artifactId>
</dependency>
To successfully export traces to the AWS X-Ray you have to run AWS Open Telemetry Collector that will periodically send traces to the AWS.
6.7 HTTP Server and Client
To enable creating span objects on the every HTTP server request, client request, server response and client response you have to add next depedency:
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry-http")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-opentelemetry-http</artifactId>
</dependency>
Filtering HTTP spans
It may be useful to exclude health-checks and other HTTP requests to your service. This can be achieved by adding a list of regular expression patterns to your configuration:
otel.exclusions[0]=/health
otel.exclusions[1]=/env/.*
otel:
exclusions:
- /health
- /env/.*
[otel]
exclusions=[
"/health",
"/env/.*"
]
otel {
exclusions = ["/health", "/env/.*"]
}
{
otel {
exclusions = ["/health", "/env/.*"]
}
}
{
"otel": {
"exclusions": ["/health", "/env/.*"]
}
}
Add HTTP Headers into request spans
If you want you can add additional Http Headers inside your span objects. You can specify different headers for client request, client response, server request and server response.
otel.http.client.request-headers[0]=X-From-Client-Request
otel.http.client.response-headers[0]=X-From-Client-Response
otel.http.server.request-headers[0]=X-From-Server-Request
otel.http.server.response-headers[0]=X-From-Server-Response
otel:
http:
client:
request-headers:
- X-From-Client-Request
response-headers:
- X-From-Client-Response
server:
request-headers:
- X-From-Server-Request
response-headers:
- X-From-Server-Response
[otel]
[otel.http]
[otel.http.client]
request-headers=[
"X-From-Client-Request"
]
response-headers=[
"X-From-Client-Response"
]
[otel.http.server]
request-headers=[
"X-From-Server-Request"
]
response-headers=[
"X-From-Server-Response"
]
otel {
http {
client {
requestHeaders = ["X-From-Client-Request"]
responseHeaders = ["X-From-Client-Response"]
}
server {
requestHeaders = ["X-From-Server-Request"]
responseHeaders = ["X-From-Server-Response"]
}
}
}
{
otel {
http {
client {
request-headers = ["X-From-Client-Request"]
response-headers = ["X-From-Client-Response"]
}
server {
request-headers = ["X-From-Server-Request"]
response-headers = ["X-From-Server-Response"]
}
}
}
}
{
"otel": {
"http": {
"client": {
"request-headers": ["X-From-Client-Request"],
"response-headers": ["X-From-Client-Response"]
},
"server": {
"request-headers": ["X-From-Server-Request"],
"response-headers": ["X-From-Server-Response"]
}
}
}
}
6.8 Logback MDC
Logback MDC
OpenTelemetry provides Logback MDC instrumentation that can add the active span context to log events. This is useful when your log aggregation system reads application logs separately from traces and you want to correlate a log line with the trace and span that were active when the log event was created.
Add the OpenTelemetry Logback MDC dependency to your application:
runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-logback-mdc-1.0")
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-mdc-1.0</artifactId>
<scope>runtime</scope>
</dependency>
Then wrap your Logback appender with the OpenTelemetry MDC appender and include the MDC keys in your log pattern:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags} %msg%n</pattern>
</encoder>
</appender>
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
<appender-ref ref="CONSOLE"/>
</appender>
<root level="INFO">
<appender-ref ref="OTEL"/>
</root>
</configuration>
The default MDC keys are trace_id, span_id, and trace_flags.
These values are only added when a valid span is current, so logs written outside traced execution may not contain trace correlation fields.
See the OpenTelemetry logger MDC documentation and the OpenTelemetry Logback MDC library documentation for additional options, including custom MDC key names and baggage support.
6.9 gRPC Server and Client
To enable creating span objects on the every GRPC server request, client request, server response and client response, add the following dependency:
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry-grpc")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-opentelemetry-grpc</artifactId>
</dependency>
6.10 JDBC
To enable creating span objects on the every JDBC query, add the following dependency:
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry-jdbc")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-opentelemetry-jdbc</artifactId>
</dependency>
The dependency instruments the javax.sql.DataSource beans for intercepting the queries to the database.
6.11 R2DBC
To enable creating spans for every R2DBC query, add the following dependency:
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry-r2dbc")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-opentelemetry-r2dbc</artifactId>
</dependency>
The dependency instruments io.r2dbc.spi.ConnectionFactory beans to intercept database queries.
6.12 Kafka
To enable creating span objects on the every Kafka message, add the following depedency:
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry-kafka")
<dependency>
<groupId>io.micronaut.tracing</groupId>
<artifactId>micronaut-tracing-opentelemetry-kafka</artifactId>
</dependency>
Add Kafka Message Headers into request spans
The way Kafka Message Headers are added inside your span objects may be configured as follows:
otel.instrumentation.kafka.wrapper=true
otel.instrumentation.kafka.headers-as-lists=false
otel.instrumentation.kafka.attribute-with-prefix=true
otel.instrumentation.kafka.attribute-prefix=myPrefix
otel.instrumentation.kafka.captured-headers[0]=myHeader1
otel.instrumentation.kafka.captured-headers[1]=myHeader2
otel.instrumentation.kafka.included-topics[0]=topic1
otel.instrumentation.kafka.included-topics[1]=topic2
otel.instrumentation.kafka.excluded-topics[0]=topic1
otel.instrumentation.kafka.excluded-topics[1]=topic2
otel:
instrumentation:
kafka:
wrapper: true
headers-as-lists: false
attribute-with-prefix: true
attribute-prefix: myPrefix
captured-headers: # list of headers which need to send as span attributes
- myHeader1
- myHeader2
included-topics:
- topic1
- topic2
excluded-topics:
- topic1
- topic2
[otel]
[otel.instrumentation]
[otel.instrumentation.kafka]
wrapper=true
headers-as-lists=false
attribute-with-prefix=true
attribute-prefix="myPrefix"
captured-headers=[
"myHeader1",
"myHeader2"
]
included-topics=[
"topic1",
"topic2"
]
excluded-topics=[
"topic1",
"topic2"
]
otel {
instrumentation {
kafka {
wrapper = true
headersAsLists = false
attributeWithPrefix = true
attributePrefix = "myPrefix"
capturedHeaders = ["myHeader1", "myHeader2"]
includedTopics = ["topic1", "topic2"]
excludedTopics = ["topic1", "topic2"]
}
}
}
{
otel {
instrumentation {
kafka {
wrapper = true
headers-as-lists = false
attribute-with-prefix = true
attribute-prefix = "myPrefix"
captured-headers = ["myHeader1", "myHeader2"]
included-topics = ["topic1", "topic2"]
excluded-topics = ["topic1", "topic2"]
}
}
}
}
{
"otel": {
"instrumentation": {
"kafka": {
"wrapper": true,
"headers-as-lists": false,
"attribute-with-prefix": true,
"attribute-prefix": "myPrefix",
"captured-headers": ["myHeader1", "myHeader2"],
"included-topics": ["topic1", "topic2"],
"excluded-topics": ["topic1", "topic2"]
}
}
}
}
-
wrapper- if "true" tracing will be done using object proxies over consumers and producers. If wrappers are disabled, then tracing can be done through a kafka listener instead. -
If you want to set headers as lists, set
headers-as-liststotrue. -
attribute-with-prefix- whether to add a prefix to span attribute names (default: false) -
attribute-prefix- custom prefix for span attributes (header names). Default: messaging.header. -
captured-headersis the list of headers to add as span attributes. By default, all headers are added as span attributes. To set no headers as span attributes, specifynullor an empty string. -
included-topics- list of topics to trace -
excluded-topics- list of topics to exclude from tracing
You can’t use included-topics and excluded-topics properties together as they are mutually exclusive - choose one or the other.
|
6.13 AWS SDK Instrumentation
Include the following dependency to instrument the AWS SDK:
implementation("io.opentelemetry.instrumentation:opentelemetry-aws-sdk-2.2")
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-aws-sdk-2.2</artifactId>
</dependency>
Additionally, include Micronaut AWS SDK v2 dependency:
implementation("io.micronaut.aws:micronaut-aws-sdk-v2")
<dependency>
<groupId>io.micronaut.aws</groupId>
<artifactId>micronaut-aws-sdk-v2</artifactId>
</dependency>
micronaut-aws-sdk-v2 dependency creates a bean of type SdkClientBuilder. To instrument the AWS SDK, Micronaut OpenTelemetry registers a tracing interceptor
via a bean creation listener for the bean of type SdkClientBuilder.
6.14 AWS Resource Detectors
AWS Resource detectors enrich traces with AWS infrastructure information.
To use AWS resource detectors include the following dependency:
implementation("io.opentelemetry:opentelemetry-sdk-extension-aws")
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-aws</artifactId>
</dependency>
and provide a bean of type ResourceProvider
package io.micronaut.tracing.opentelemetry;
import io.micronaut.core.annotation.NonNull;
import io.opentelemetry.contrib.aws.resource.Ec2Resource;
import io.opentelemetry.sdk.resources.Resource;
import jakarta.inject.Singleton;
@Singleton
public class AwsResourceProvider implements ResourceProvider {
@Override
@NonNull
public Resource resource() {
return Resource.getDefault()
.merge(Ec2Resource.get());
}
}
6.15 OpenTelemetry Guides
See the following guides to learn more about distributed tracing with OpenTelemetry in the Micronaut Framework:
-
OpenTelemetry Tracing with Google Cloud Trace and the Micronaut Framework
-
OpenTelemetry Tracing with Oracle Cloud and the Micronaut Framework
-
Use OpenTelemetry with Jaeger and the Micronaut Framework for Microservice Distributed Tracing
-
Microservices Distributed Tracing with OpenTelemetry Through X-Ray and the Micronaut Framework
-
Use OpenTelemetry with Zipkin and the Micronaut Framework for Microservice Distributed Tracing
7 Micronaut Tracing Guides
See the following list of guides to learn more about working with Distributed Tracing in the Micronaut Framework:
8 Repository
You can find the source code of this project in this repository: