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();
+```