diff --git a/content/en/docs/instrumentation/java/exporters.md b/content/en/docs/instrumentation/java/exporters.md new file mode 100644 index 000000000000..7cc6f2e75094 --- /dev/null +++ b/content/en/docs/instrumentation/java/exporters.md @@ -0,0 +1,141 @@ +--- +title: Exporters +weight: 50 +cSpell:ignore: autoconfigure springframework +--- + +In order to visualize and analyze your traces, you will need to export them to a +backend such as [Jaeger](https://www.jaegertracing.io/) or +[Zipkin](https://zipkin.io/). OpenTelemetry Java provides exporters for some +common open source backends. + +If you use the Java agent for +[automatic instrumentation](/docs/instrumentation/java/automatic) you can learn +how to setup exporters following the +[Agent Configuration Guide](/docs/instrumentation/java/automatic/agent-config) + +For [manual instrumentation](/docs/instrumentation/java/manual), you will find +some introductions below on how to set up backends and the matching exporters. + +## OTLP endpoint + +To send trace data to a OTLP endpoint (like the [collector](/docs/collector) or +Jaeger) you'll want to use `opentelemetry-exporter-otlp`: + +{{< tabpane text=true >}} {{% tab Gradle %}} + +```kotlin +dependencies { + implementation 'io.opentelemetry:opentelemetry-exporter-otlp:{{% param javaVersion %}}' +} +``` + +{{% /tab %}} {{% tab Maven %}} + +```xml + + + + io.opentelemetry + opentelemetry-exporter-otlp + + + +``` + +{{< /tab >}} {{< /tabpane>}} + +Next, configure the exporter to point at an OTLP endpoint. + +If you use +[SDK auto-configuration](/docs/instrumentation/java/manual/#automatic-configuration) +all you need to do is update your environment variables: + +```shell +env OTEL_EXPORTER_OTLP_ENDPOINT=http://example:4317 java -jar ./build/libs/java-simple.jar +``` + +Note, that in the case of exporting via OTLP you do not need to set +`OTEL_TRACES_EXPORTER`, `OTEL_METRICS_EXPORTER` and `OTEL_LOGS_EXPORTER` since +`otlp` is their default value + +In the case of [manual configuration] you can update the +[example app](/docs/instrumentation/java/manual#example-app) like the following: + +```java { hl_lines=["12-14",21,"39-53"] } +package otel; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.Banner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; + +@SpringBootApplication +public class DiceApplication { + public static void main(String[] args) { + SpringApplication app = new SpringApplication(DiceApplication.class); + app.setBannerMode(Banner.Mode.OFF); + app.run(args); + } + + @Bean + public OpenTelemetry openTelemetry() { + Resource resource = Resource.getDefault().toBuilder().put(SERVICE_NAME, "dice-server").put(SERVICE_VERSION, "0.1.0").build(); + + SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder().build()).build()) + .setResource(resource) + .build(); + + SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder() + .registerMetricReader(PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()).build()) + .setResource(resource) + .build(); + + SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder() + .addLogRecordProcessor( + BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.builder().build()).build()) + .setResource(resource) + .build(); + + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .setMeterProvider(sdkMeterProvider) + .setLoggerProvider(sdkLoggerProvider) + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .buildAndRegisterGlobal(); + + return openTelemetry; + } +} +``` + +To see the traces exported quickly, you can run Jaeger with OTLP enabled in a +docker container: + +```shell +docker run -d --name jaeger \ + -e COLLECTOR_OTLP_ENABLED=true \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + jaegertracing/all-in-one:latest +``` diff --git a/content/en/docs/instrumentation/java/libraries.md b/content/en/docs/instrumentation/java/libraries.md new file mode 100644 index 000000000000..a89cfbba7c1a --- /dev/null +++ b/content/en/docs/instrumentation/java/libraries.md @@ -0,0 +1,7 @@ +--- +title: Using instrumentation libraries +linkTitle: Libraries +weight: 40 +--- + +_to be done_ diff --git a/content/en/docs/instrumentation/java/manual.md b/content/en/docs/instrumentation/java/manual.md index 5b299bcc41e7..5c8ba8fabf77 100644 --- a/content/en/docs/instrumentation/java/manual.md +++ b/content/en/docs/instrumentation/java/manual.md @@ -7,31 +7,203 @@ aliases: - /docs/instrumentation/java/manual_instrumentation weight: 30 description: Manual instrumentation for OpenTelemetry Java -cSpell:ignore: autoconfigure classpath customizer logback loggable multivalued +# prettier-ignore +cSpell:ignore: autoconfigure Autowired classpath customizer logback loggable multivalued rolldice springframework --- {{% docs/instrumentation/manual-intro %}} -## Setup +{{% alert title="Note" color="info" %}} -The first step is to get a handle to an instance of the `OpenTelemetry` -interface. +On this page you will learn how you can add traces, metrics and logs to your +code _manually_. But, you are not limited to only use one kind of +instrumentation: use +[automatic instrumentation](/docs/instrumentation/java/automatic/) to get +started and then enrich your code with manual instrumentation as needed. -If you are an application developer, you need to configure an instance of the -`OpenTelemetrySdk` as early as possible in your application. This can be done -using the `OpenTelemetrySdk.builder()` method. The returned -`OpenTelemetrySdkBuilder` instance gets the providers related to the signals, -tracing and metrics, in order to build the `OpenTelemetry` instance. +Note, that especially if you cannot modify the source code of your app, you can +skip manual instrumentation and only use automatic instrumentation. -You can build the providers by using the `SdkTracerProvider.builder()` and -`SdkMeterProvider.builder()` methods. It is also strongly recommended to define -a `Resource` instance as a representation of the entity producing the telemetry; -in particular the `service.name` attribute is the most important piece of -telemetry source-identifying info. +Also, for libraries your code depends on, you don't have to write +instrumentation code yourself, since they might come with OpenTelemetry built-in +_natively_ or you can make use of +[instrumentation libraries](/docs/instrumentation/java/libraries/). + +{{% /alert %}} + +## Example app preparation {#example-app} + +This page uses a modified version of the example app from +[Getting Started](/docs/instrumentation/java/getting-started/) to help you learn +about manual instrumentation. + +You don't have to use the example app: if you want to instrument your own app or +library, follow the instructions here to adapt the process to your own code. + +### Dependencies {#example-app-dependencies} + +To begin, set up an environment in a new directory called `java-simple`. Within +that directory, create a file called `build.gradle.kts` with the following +content: + +```kotlin +plugins { + id("java") + id("org.springframework.boot") version "3.0.6" + id("io.spring.dependency-management") version "1.1.0" +} + +sourceSets { + main { + java.setSrcDirs(setOf(".")) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") +} +``` + +### Create and launch an HTTP Server + +To highlight the difference between instrumenting a _library_ and a standalone +_app_, split out the dice rolling into a _library_ class, which then will be +imported as a dependency by the app. + +Create the _library file_ name `Dice.java` and add the following code to it: + +```Java +package otel; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class Dice { + + private int min; + private int max; + + public Dice(int min, int max) { + this.min = min; + this.max = max; + } + + public List rollTheDice(int rolls) { + List results = new ArrayList(); + for (int i = 0; i < rolls; i++) { + results.add(this.rollOnce()); + } + return results; + } + + private int rollOnce() { + return ThreadLocalRandom.current().nextInt(this.min, this.max + 1); + } +} +``` + +Create the app files `DiceApplication.java` and `RollController.java` and add +the following code to them: + +```java +// DiceApplication.java +package otel; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.Banner; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DiceApplication { + public static void main(String[] args) { + + SpringApplication app = new SpringApplication(DiceApplication.class); + app.setBannerMode(Banner.Mode.OFF); + app.run(args); + } +} +``` + +```java +// RollController.java +package otel; + +import java.util.List; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import otel.Dice; + +@RestController +public class RollController { + private static final Logger logger = LoggerFactory.getLogger(RollController.class); + + @GetMapping("/rolldice") + public List index(@RequestParam("player") Optional player, + @RequestParam("rolls") Optional rolls) { + + if (!rolls.isPresent()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing rolls parameter", null); + } -### Maven + List result = new Dice(1, 6).rollTheDice(rolls.get()); + + if (player.isPresent()) { + logger.info("{} is rolling the dice: {}", player.get(), result); + } else { + logger.info("Anonymous player is rolling the dice: {}", result); + } + return result; + } +} +``` + +To ensure that it is working, run the application with the following command and +open in your web browser: + +```shell +gradle assemble +java -jar ./build/libs/java-simple.jar +``` + +You should get a list of 12 numbers in your browser window, for example: + +```text +[5,6,5,3,6,1,2,5,4,4,2,4] +``` + +## Manual instrumentation setup + +For both library and app instrumentation, the first step is to install the +dependencies for the OpenTelemetry API. + +{{< tabpane text=true >}} {{% tab Gradle %}} + +```kotlin { hl_lines=3 } +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web"); + implementation("io.opentelemetry:opentelemetry-api:{{% param javaVersion %}}"); +} +``` + +Throughout this documentation you will add additional dependencies. For a full +list of artifact coordinates see [releases][releases] and for semantic +convention releases see [semantic-conventions-java][semantic-conventions-java]. + +{{% /tab %}} {{% tab Maven %}} ```xml @@ -46,7 +218,53 @@ telemetry source-identifying info. + + + io.opentelemetry + opentelemetry-api + + + +``` + +{{% /tab %}} {{% /tabpane %}} + +### Initialize the SDK +{{% alert title="Note" color="info" %}} If you’re instrumenting a library, +**skip this step**. {{% /alert %}} + +If you instrument a Java app, install the dependencies for the OpenTelemetry +SDK. + +{{< tabpane text=true >}} {{% tab Gradle %}} + +```kotlin { hl_lines="4-8" } +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web"); + implementation("io.opentelemetry:opentelemetry-api:{{% param javaVersion %}}"); + implementation("io.opentelemetry:opentelemetry-sdk:{{% param javaVersion %}}"); + implementation("io.opentelemetry:opentelemetry-sdk-metrics:{{% param javaVersion %}}"); + implementation("io.opentelemetry:opentelemetry-exporter-logging:{{% param javaVersion %}}"); + implementation("io.opentelemetry:opentelemetry-semconv:{{% param javaVersion %}}-alpha"); +} +``` + +{{% /tab %}} {{% tab Maven %}} + +```xml + + + + + io.opentelemetry + opentelemetry-bom + {{% param javaVersion %}} + pom + import + + + io.opentelemetry @@ -55,10 +273,14 @@ telemetry source-identifying info. io.opentelemetry opentelemetry-sdk + + + io.opentelemetry + opentelemetry-sdk-metrics io.opentelemetry - opentelemetry-exporter-otlp + opentelemetry-exporter-logging @@ -70,85 +292,258 @@ telemetry source-identifying info. ``` -See [releases][releases] for a full list of artifact coordinates. +{{% /tab %}} {{< /tabpane>}} -See [semantic-conventions-java][semantic-conventions-java] for semantic -conventions releases. +If you are an application developer, you need to configure an instance of the +`OpenTelemetrySdk` as early as possible in your application. This can either be +done manually by using the `OpenTelemetrySdk.builder()` or by using the SDK +auto-configuration extension through the +`opentelemetry-sdk-extension-autoconfigure` module. It is recommended to use +auto-configuration, as it is easier to use and comes with various additional +capabilities. -### Gradle +#### Automatic Configuration -```kotlin +To use auto-configuration add the following dependency to your application: + +{{< tabpane text=true >}} {{% tab Gradle %}} + +```kotlin { hl_lines="9-10" } dependencies { - implementation 'io.opentelemetry:opentelemetry-api:{{% param javaVersion %}}' - implementation 'io.opentelemetry:opentelemetry-sdk:{{% param javaVersion %}}' - implementation 'io.opentelemetry:opentelemetry-exporter-otlp:{{% param javaVersion %}}' - implementation 'io.opentelemetry.semconv:opentelemetry-semconv:{{% param semconvJavaVersion %}}-alpha' + implementation("org.springframework.boot:spring-boot-starter-web"); + implementation("io.opentelemetry:opentelemetry-api:{{% param javaVersion %}}"); + implementation("io.opentelemetry:opentelemetry-sdk:{{% param javaVersion %}}"); + implementation("io.opentelemetry:opentelemetry-sdk-metrics:{{% param javaVersion %}}"); + implementation("io.opentelemetry:opentelemetry-exporter-logging:{{% param javaVersion %}}"); + implementation("io.opentelemetry.semconv:opentelemetry-semconv:{{% param semconvJavaVersion %}}-alpha") + implementation("opentelemetry-sdk-extension-autoconfigure:{{% param javaVersion %}}"); + implementation("opentelemetry-sdk-extension-autoconfigure-spi:{{% param javaVersion %}}"); } ``` -See [releases][releases] for a full list of artifact coordinates. +{{% /tab %}} {{% tab Maven %}} + +```xml + + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure-spi + + + +``` + +{{% /tab %}} {{< /tabpane>}} + +It allows you to auto-configure the OpenTelemetry SDK based on a standard set of +supported environment variables and system properties. Each environment variable +has a corresponding system property named the same way but as lower case and +using the `.` (dot) character instead of the `_` (underscore) as separator. + +The logical service name can be specified via the `OTEL_SERVICE_NAME` +environment variable (or `otel.service.name` system property). -See [semantic-conventions-java][semantic-conventions-java] for semantic -conventions releases. +The traces, metrics or logs exporters can be set via the `OTEL_TRACES_EXPORTER`, +`OTEL_METRICS_EXPORTER` and `OTEL_LOGS_EXPORTER` environment variables. For +example `OTEL_TRACES_EXPORTER=logging` configures your application to use an +exporter that writes all traces to the console. The corresponding exporter +library has to be provided in the classpath of the application as well. -### Imports +For debugging and local development purposes, use the `logging` exporter. After +you have finished setting up manual instrumentation, provide an appropriate +exporter library in the classpath of the application to +[export the app's telemetry data](/docs/instrumentation/java/exporters/) to one +or more telemetry backends. + +The SDK auto-configuration has to be initialized as early as possible in the +application lifecycle in order to allow the module to go through the provided +environment variables (or system properties) and set up the `OpenTelemetry` +instance by using the builders internally. ```java +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; + +OpenTelemetrySdk sdk = AutoConfiguredOpenTelemetrySdk.initialize() + .getOpenTelemetrySdk(); +``` + +In the case of the [example app](#example-app) the `DiceApplication` class gets +updated as follows: + +```java { hl_lines=["6-9","19-22"] } +package otel; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.Banner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; + +@SpringBootApplication +public class DiceApplication { + public static void main(String[] args) { + SpringApplication app = new SpringApplication(DiceApplication.class); + app.setBannerMode(Banner.Mode.OFF); + app.run(args); + } + + @Bean + public OpenTelemetry openTelemetry() { + return AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk(); + } +} +``` + +To verify your code, build and run the app: + +```sh +gradle assemble +env \ +OTEL_SERVICE_NAME=dice-server \ +OTEL_TRACES_EXPORTER=logging \ +OTEL_METRICS_EXPORTER=logging \ +OTEL_LOGS_EXPORTER=logging \ +java -jar ./build/libs/java-simple.jar +``` + +This basic setup has no effect on your app yet. You need to add code for +[traces](#traces), [metrics](#metrics), and/or [logs](#logs). + +#### Manual Configuration + +`OpenTelemetrySdk.builder()` returns an instance of `OpenTelemetrySdkBuilder`, +which gets the providers related to the signals, tracing and metrics, in order +to build the `OpenTelemetry` instance. + +You can build the providers by using the `SdkTracerProvider.builder()` and +`SdkMeterProvider.builder()` methods. + +In the case of the [example app](#example-app) the the `DiceApplication` class +gets updated as follows: + +```java { hl_lines=["6-24","34-62"] } +package otel; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.Banner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; -import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; -import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.exporter.logging.LoggingMetricExporter; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; 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.SimpleSpanProcessor; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; import io.opentelemetry.semconv.ResourceAttributes; + +@SpringBootApplication +public class DiceApplication { + public static void main(String[] args) { + SpringApplication app = new SpringApplication(DiceApplication.class); + app.setBannerMode(Banner.Mode.OFF); + app.run(args); + } + + @Bean + public OpenTelemetry openTelemetry() { + Resource resource = Resource.getDefault().toBuilder().put(ResourceAttributes.SERVICE_NAME, "dice-server").put(ResourceAttributes.SERVICE_VERSION, "0.1.0").build(); + + SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) + .setResource(resource) + .build(); + + SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder() + .registerMetricReader(PeriodicMetricReader.builder(LoggingMetricExporter.create()).build()) + .setResource(resource) + .build(); + + SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder() + .addLogRecordProcessor(BatchLogRecordProcessor.builder(SystemOutLogRecordExporter.create()).build()) + .setResource(resource) + .build(); + + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .setMeterProvider(sdkMeterProvider) + .setLoggerProvider(sdkLoggerProvider) + .setPropagators(ContextPropagators.create(TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance()))) + .buildAndRegisterGlobal(); + + return openTelemetry; + } +} ``` -### Example +For debugging and local development purposes, the example exports telemetry to +the console. After you have finished setting up manual instrumentation, you need +to configure an appropriate exporter to +[export the app's telemetry data](/docs/instrumentation/java/exporters/) to one +or more telemetry backends. -```java -Resource resource = Resource.getDefault() - .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "logical-service-name"))); +The example also sets up the mandatory SDK default attribute `service.name`, +which holds the logical name of the service, and the optional (but highly +encouraged!) attribute `service.version`, which holds the version of the service +API or implementation. -SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder().build()).build()) - .setResource(resource) - .build(); +Alternative methods exist for setting up resource attributes. For more +information, see [Resources](/docs/instrumentation/java/resources/). -SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder() - .registerMetricReader(PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()).build()) - .setResource(resource) - .build(); +To verify your code, build and run the app: + +```sh +gradle assemble +java -jar ./build/libs/java-simple.jar +``` + +This basic setup has no effect on your app yet. You need to add code for +[traces](#traces), [metrics](#metrics), and/or [logs](#logs). + +## Traces + +### Initialize Tracing + +{{% alert title="Note" color="info" %}} If you’re instrumenting a library, +**skip this step**. {{% /alert %}} + +To enable [tracing](/docs/concepts/signals/traces/) in your app, you'll need to +have an initialized +[`TracerProvider`](/docs/concepts/signals/traces/#tracer-provider) that will let +you create a [`Tracer`](/docs/concepts/signals/traces/#tracer): -SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder() - .addLogRecordProcessor(BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.builder().build()).build()) +```java +import io.opentelemetry.sdk.trace.SdkTracerProvider; + +SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(spanProcessor) .setResource(resource) .build(); - -OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(sdkTracerProvider) - .setMeterProvider(sdkMeterProvider) - .setLoggerProvider(sdkLoggerProvider) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .buildAndRegisterGlobal(); ``` -As an aside, if you are writing library instrumentation, it is strongly -recommended that you provide your users the ability to inject an instance of -`OpenTelemetry` into your instrumentation code. If this is not possible for some -reason, you can fall back to using an instance from the `GlobalOpenTelemetry` -class. Note that you can't force end users to configure the global, so this is -the most brittle option for library instrumentation. +If a `TracerProvider` is not created, the OpenTelemetry APIs for tracing will +use a no-op implementation and fail to generate data. -## Traces +If you followed the instructions to [initialize the SDK](#initialize-the-sdk) +above, you have a `TracerProvider` setup for you already. You can continue with +[acquiring a tracer](#acquiring-a-tracer). ### Acquiring a Tracer @@ -164,38 +559,167 @@ instrumenting][instrumentation library] the [instrumented library] or application to be monitored. More information is available in the specification chapter [Obtaining a Tracer]. +Anywhere in your application where you write manual tracing code should call +`getTracer` to acquire a tracer. For example: + ```java -import io.opentelemetry.api; +import io.opentelemetry.api.trace.Tracer; -//... +Tracer tracer = openTelemetry.getTracer("instrumentation-scope-name", "instrumentation-scope-version"); +``` + +The values of `instrumentation-scope-name` and `instrumentation-scope-version` +should uniquely identify the +[instrumentation scope](/docs/specs/otel/glossary/#instrumentation-scope), such +as the package, module or class name. This will help later help determining what +the source of telemetry is. While the name is required, the version is still +recommended despite being optional. Note, that all `Tracer`s that are created by +a single `OpenTelemetry` instance will interoperate, regardless of name. + +It's generally recommended to call `getTracer` in your app when you need it +rather than exporting the `tracer` instance to the rest of your app. This helps +avoid trickier application load issues when other required dependencies are +involved. + +In the case of the [example app](#example-app), there are two places where a +tracer may be acquired with an appropriate instrumentation scope: + +First, in the `index` method of the `RollController` as follows: + +```java { hl_lines=["4-6",11,"13-16"] } +package otel; -Tracer tracer = - openTelemetry.getTracer("instrumentation-library-name", "1.0.0"); +// ... +import org.springframework.beans.factory.annotation.Autowired; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; + +@RestController +public class RollController { + private static final Logger logger = LoggerFactory.getLogger(RollController.class); + private final Tracer tracer; + + @Autowired + RollController(OpenTelemetry openTelemetry) { + tracer = openTelemetry.getTracer(RollController.class.getName(), "0.1.0"); + } + // ... +} ``` -Important: the "name" and optional version of the tracer are purely -informational. All `Tracer`s that are created by a single `OpenTelemetry` -instance will interoperate, regardless of name. +And second, in the _library file_ `Dice.java`: + +```java { hl_lines=["2-3","9-19"]} +// ... +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; + +public class Dice { + + private int min; + private int max; + private Tracer tracer; + + public Dice(int min, int max, OpenTelemetry openTelemetry) { + this.min = min; + this.max = max; + this.tracer = openTelemetry.getTracer(Dice.class.getName(), "0.1.0"); + } + + public Dice(int min, int max) { + this(min, max, OpenTelemetry.noop()) + } + + // ... +} +``` + +As an aside, if you are writing library instrumentation, it is strongly +recommended that you provide your users the ability to inject an instance of +`OpenTelemetry` into your instrumentation code. If this is not possible for some +reason, you can fall back to using an instance from the `GlobalOpenTelemetry` +class: + +```java +import io.opentelemetry.api.GlobalOpenTelemetry; + +Tracer tracer = GlobalOpenTelemetry.getTracer("instrumentation-scope-name", "instrumentation-scope-version"); +``` + +Note that you can't force end users to configure the global, so this is the most +brittle option for library instrumentation. ### Create Spans +Now that you have [tracers](/docs/concepts/signals/traces/#tracer) initialized, +you can create [spans](/docs/concepts/signals/traces/#spans). + To create [Spans](/docs/concepts/signals/traces/#spans), 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(); +The code below illustrates how to create a span: -// Make the span the current span -try (Scope ss = span.makeCurrent()) { - // In this scope, the span is the current/active span -} finally { - span.end(); -} +```java { hl_lines=["1-2","8-11","19-21"] } +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; + +// ... + @GetMapping("/rolldice") + public List index(@RequestParam("player") Optional player, + @RequestParam("rolls") Optional rolls) { + Span span = tracer.spanBuilder("rollTheDice").startSpan(); + + // Make the span the current span + try (Scope scope = span.makeCurrent()) { + + if (!rolls.isPresent()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing rolls parameter", null); + } + + List result = new Dice(1, 6).rollTheDice(rolls.get()); + + if (player.isPresent()) { + logger.info("{} is rolling the dice: {}", player.get(), result); + } else { + logger.info("Anonymous player is rolling the dice: {}", result); + } + return result; + } catch(Throwable t) { + span.recordException(t); + throw t; + } finally { + span.end(); + } + } ``` It's required to call `end()` to end the span when you want it to end. +If you followed the instructions using the [example app](#example-app) up to +this point, you can copy the code above into the `index` method of the +`RollController`. You should now be able to see spans emitted from your app. + +Start your app as follows, and then send it requests by visiting + with your browser or `curl`: + +```shell +gradle assemble +env \ +OTEL_SERVICE_NAME=dice-server \ +OTEL_TRACES_EXPORTER=logging \ +OTEL_METRICS_EXPORTER=logging \ +OTEL_LOGS_EXPORTER=logging \ +java -jar ./build/libs/java-simple.jar +``` + +After a while, you should see the spans printed in the console by the +`LoggingSpanExporter`, something like this: + +```log +2023-08-02T17:22:22.658+02:00 INFO 2313 --- [nio-8080-exec-1] i.o.e.logging.LoggingSpanExporter : 'rollTheDice' : 565232b11b9933fa6be8d6c4a1307fe2 6e1e011e2e8c020b INTERNAL [tracer: otel.RollController:0.1.0] {} +``` + ### Create nested Spans Most of the time, we want to correlate @@ -204,52 +728,68 @@ OpenTelemetry supports tracing within processes and across remote processes. For more details 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: +For example in the `Dice` class method `rollTheDice` calling method `rollOnce`, +the spans could be manually linked in the following way: -```java -void parentOne() { - Span parentSpan = tracer.spanBuilder("parent").startSpan(); - try { - childOne(parentSpan); - } finally { - parentSpan.end(); +```java { hl_lines=["1-2","5","7","9","12-14","17-21","23-25"]} +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +// ... + public List rollTheDice(int rolls) { + Span parentSpan = tracer.spanBuilder("parent").startSpan(); + List results = new ArrayList(); + try { + for (int i = 0; i < rolls; i++) { + results.add(this.rollOnce(parentSpan)); + } + return results; + } finally { + parentSpan.end(); + } } -} -void childOne(Span parentSpan) { - Span childSpan = tracer.spanBuilder("child") + private int rollOnce(Span parentSpan) { + Span childSpan = tracer.spanBuilder("child") .setParent(Context.current().with(parentSpan)) .startSpan(); - try { - // do stuff - } finally { - childSpan.end(); + try { + return ThreadLocalRandom.current().nextInt(this.min, this.max + 1); + } finally { + childSpan.end(); + } } -} ``` The OpenTelemetry API offers also an automated way to propagate the parent span on the current thread: -```java -void parentTwo() { - Span parentSpan = tracer.spanBuilder("parent").startSpan(); - try(Scope scope = parentSpan.makeCurrent()) { - childTwo(); - } finally { - parentSpan.end(); +```java { hl_lines=["1-2","5-6","12-14","18-22","24-26"]} +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +// ... + public List rollTheDice(int rolls) { + Span parentSpan = tracer.spanBuilder("parent").startSpan(); + try (Scope scope = parentSpan.makeCurrent()) { + List results = new ArrayList(); + for (int i = 0; i < rolls; i++) { + results.add(this.rollOnce()); + } + return results; + } finally { + parentSpan.end(); + } } -} -void childTwo() { - Span childSpan = tracer.spanBuilder("child") + + private int rollOnce() { + Span childSpan = tracer.spanBuilder("child") // NOTE: setParent(...) is not required; // `Span.current()` is automatically added as the parent .startSpan(); - try(Scope scope = childSpan.makeCurrent()) { - // do stuff - } finally { - childSpan.end(); + try(Scope scope = childSpan.makeCurrent()) { + return ThreadLocalRandom.current().nextInt(this.min, this.max + 1); + } finally { + childSpan.end(); + } } } ``` @@ -301,7 +841,15 @@ defined in the specification at First add the semantic conventions as a dependency to your application: -#### Maven +{{< tabpane text=true >}} {{% tab Gradle %}} + +```kotlin +dependencies { + implementation("io.opentelemetry.semconv:opentelemetry-semconv:{{% param semconvJavaVersion %}}-alpha") +} +``` + +{{% /tab %}} {{% tab Maven %}} ```xml @@ -311,13 +859,7 @@ First add the semantic conventions as a dependency to your application: ``` -#### Gradle - -```kotlin -dependencies { - implementation("io.opentelemetry.semconv:opentelemetry-semconv:{{% param semconvJavaVersion %}}-alpha") -} -``` +{{% /tab %}} {{< /tabpane>}} Finally, you can update your file to include semantic attributes: diff --git a/content/en/docs/instrumentation/java/resources.md b/content/en/docs/instrumentation/java/resources.md new file mode 100644 index 000000000000..baa15aee2329 --- /dev/null +++ b/content/en/docs/instrumentation/java/resources.md @@ -0,0 +1,112 @@ +--- +title: Resources +weight: 70 +cSpell:ignore: getenv myhost SIGINT uuidgen WORKDIR +--- + +A [resource](/docs/specs/otel/resource/sdk/) represents the entity producing +telemetry as resource attributes. For example, a process producing telemetry +that is running in a container on Kubernetes has a Pod name, a namespace, and +possibly a deployment name. All three of these attributes can be included in the +resource. + +In your observability backend, you can use resource information to better +investigate interesting behavior. For example, if your trace or metrics data +indicate latency in your system, you can narrow it down to a specific container, +pod, or Kubernetes deployment. + +If you use the Javaagent for +[automatic instrumentation](/docs/instrumentation/java/automatic) you can learn +how to setup resource detection following the +[Agent Configuration Guide](/docs/instrumentation/java/automatic/agent-config). + +For manual instrumentation, you will find some introductions on how to set up +resource detection below. + +## Detecting resources from common environments + +You can use `ResourceProvider`s for filling in attributes related to common +environments, like +[Container](/docs/specs/otel/resource/semantic_conventions/container/), +[Host](/docs/specs/otel/resource/semantic_conventions/host/) or +[Operating System](/docs/specs/otel/resource/semantic_conventions/os/). These +can be used with or without +[auto-configuration](/docs/instrumentation/java/manual/#automatic-configuration). + +To use those providers, add the following dependency: + +{{< tabpane text=true >}} {{% tab Gradle %}} + +```kotlin +dependencies { + implementation("io.opentelemetry.instrumentation:opentelemetry-resources:{{% param javaVersion %}}-alpha"); +} +``` + +{{% /tab %}} {{% tab Maven %}} + +```xml + + + + io.opentelemetry.instrumentation + opentelemetry-resources + + + +``` + +{{< /tab >}} {{< /tabpane>}} + +Next you can use them like the following in your code: + +```java +import io.opentelemetry.instrumentation.resources.ContainerResource; +import io.opentelemetry.instrumentation.resources.HostResource; +import io.opentelemetry.instrumentation.resources.OsResource; +import io.opentelemetry.instrumentation.resources.ProcessResource; +import io.opentelemetry.instrumentation.resources.ProcessRuntimeResource; + +... + Resource resource = Resource.getDefault() + .merge(ContainerResource.get()) + .merge(HostResource.get()) + .merge(OsResource.get()) + .merge(ProcessResource.get()) + .merge(ProcessRuntimeResource.get()) + .merge(Resource.create(Attributes.builder() + .put(ResourceAttributes.SERVICE_NAME, "dice-service") + ... + .build())); +... +``` + +## Adding resources in code + +Custom resources can be configured in your code like the following: + +```java +Resource resource = Resource.getDefault() + .merge(Resource.create(Attributes.builder() + .put(ResourceAttributes.SERVICE_NAME, "dice-service") + .put(ResourceAttributes.SERVICE_VERSION, "0.1.0") + .put(ResourceAttributes.SERVICE_INSTANCE_ID, "dice-service-1") + .put(ResourceAttributes.HOST_NAME, System.getenv("HOSTNAME")) + .put(ResourceAttributes.PROCESS_PID, ProcessHandle.current().pid()) + .build())); + +SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .setResource(resource) + ... + .build(); + +SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder() + .setResource(resource) + ... + .build(); + +SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder() + .setResource(resource) + ... + .build(); +```