Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@
"matchUpdateTypes": ["major"],
"enabled": false
},
{
// Spring starter doesn't support Spring Boot 4 yet
"matchPackageNames": ["org.springframework.boot"],
"matchFilePatterns": [
"spring-declarative-configuration/build.gradle.kts"
],
"matchUpdateTypes": ["major"],
"enabled": false
},
{
// Skip locally built dice image used in logging-k8s-stdout-otlp-json
"matchManagers": ["kubernetes"],
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/oats-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- .mise/tasks/oats-tests.sh
- 'logging-k8s-stdout-otlp-json/**'
- 'javaagent-declarative-configuration/**'
- 'spring-declarative-configuration/**'
- 'doc-snippets/extensions-minimal/**'
workflow_dispatch:

Expand Down
8 changes: 6 additions & 2 deletions .mise/tasks/oats-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ pushd javaagent-declarative-configuration
../gradlew clean bootJar
popd

oats -timeout 5m logging-k8s-stdout-otlp-json/
oats -timeout 5m javaagent-declarative-configuration/oats/
pushd spring-declarative-configuration
../gradlew clean bootJar
popd

# timeout for each test suite is 5 minutes
oats -timeout 5m .
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ To build the all of examples, run:
OpenTelemetry SDK to use a Zipkin exporter and send spans to a
Zipkin backend using the OpenTelemetry API.
- Note: This example requires Docker to be installed.
- [Declarative Configuration with the OpenTelemetry Java Agent](javaagent-declarative-configuration)
- This module demonstrates how to use declarative configuration with the
OpenTelemetry Java Agent to configure tracing behavior, including
excluding specific endpoints from tracing.
- Note: This example requires Java 17 or higher.
- [Declarative Configuration with the OpenTelemetry Spring Boot Starter](spring-declarative-configuration)
- This module demonstrates how to use declarative configuration with the
OpenTelemetry Spring Boot Starter to configure tracing behavior,
including excluding specific endpoints from tracing.
- Note: This example requires Java 17 or higher.

## Contributing

Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ include(
":opentelemetry-examples-telemetry-testing",
":opentelemetry-examples-zipkin",
":opentelemetry-examples-spring-native",
":opentelemetry-examples-spring-declarative-configuration",
":opentelemetry-examples-kotlin-extension",
":opentelemetry-examples-grpc",
":opentelemetry-examples-resource-detection-gcp",
Expand Down
281 changes: 281 additions & 0 deletions spring-declarative-configuration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
# Spring Boot Declarative Configuration Example

This example demonstrates how to
use [declarative configuration](https://opentelemetry.io/docs/specs/otel/configuration/#declarative-configuration)
with the OpenTelemetry Spring Boot Starter to configure tracing, metrics, and logging for a Spring
Boot application.

Instead of using the OpenTelemetry Java Agent, this module uses the
[OpenTelemetry Spring Boot Starter](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/spring/spring-boot-autoconfigure)
and configures declarative behavior via standard Spring Boot configuration in `application.yaml`.

The main configuration file for this example is:

- [`src/main/resources/application.yaml`](./src/main/resources/application.yaml)

For the underlying declarative configuration schema and additional examples, see the
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
repository.
Remember that when you copy examples from that repository into a Spring Boot app, you must nest them
under the `otel:` root node (two-space indentation for all keys shown below).

This Spring Boot application includes two endpoints:

- `/actuator/health` – A health check endpoint (from Spring Boot Actuator) that is configured to be
**excluded** from tracing
- `/api/example` – A simple API endpoint that **will be traced** normally

## End-to-End Instructions

### Prerequisites

- Java 17 or higher
- OpenTelemetry Spring Boot Starter **2.22.0+** on the classpath of this application (already
configured in this example’s [build file](./build.gradle.kts))

### Step 1: Build the Application

From this `spring-declarative-configuration` directory:

```bash
../gradlew bootJar
```

This builds the Spring Boot fat JAR for the example application.

### Step 2: Run the Spring Boot Application

Run the application as a normal Spring Boot JAR – no `-javaagent` flag and no separate
`otel-agent-config.yaml` file
are needed. Declarative configuration is picked up from `application.yaml` by the Spring Boot
Starter.

```bash
java -jar build/libs/spring-declarative-configuration.jar
```

The Spring Boot Starter will automatically:

- Initialize OpenTelemetry SDK
- Read declarative configuration under the `otel:` root in `application.yaml`
- Apply exporters, processors, and sampler rules defined there

### Step 3: Test the Endpoints

In a separate terminal, call both endpoints:

```bash
# This endpoint will NOT be traced (excluded by declarative configuration)
curl http://localhost:8080/actuator/health

# This endpoint WILL be traced normally
curl http://localhost:8080/api/example
```

### Step 4: Verify Tracing, Metrics, and Logs

By default, this example configures:

- An OTLP HTTP exporter for traces, metrics, and logs
- A console exporter for traces for easy local inspection

Check the application logs to see that:

- Health check requests (`/actuator/health`) **do not** generate spans (dropped by sampler rules)
- Requests to `/api/example` **do** generate spans and are exported to console and/or OTLP

If you have an OTLP-compatible backend (e.g., the OpenTelemetry Collector, Jaeger, Tempo, etc.)
listening on
`http://localhost:4318`, you can inspect the exported telemetry there as well.

## Declarative Configuration with Spring Boot

The declarative configuration used by this example lives in [`application.yaml`](./src/main/resources/application.yaml)
under the `otel:` root key.

```yaml
otel:
# ... see src/main/resources/application.yaml for the full configuration
```

This layout follows the declarative configuration schema defined in the
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
repository, but adapted for Spring Boot:

- All OpenTelemetry configuration keys live under the `otel:` root
- Configuration blocks from the reference repo (such as `tracer_provider`, `meter_provider`,
`logger_provider`, etc.) are indented by **two spaces** beneath `otel:`
- The configuration is loaded via the OpenTelemetry Spring Boot Starter instead of the Java Agent

### Opting In with `file_format`

Declarative configuration is **opt-in**. In this Spring Boot example, you enable declarative
configuration by setting `file_format` under `otel:` in `application.yaml`:

```yaml
otel:
file_format: "1.0-rc.2"
# ... other configuration
```

The `file_format` value follows the versions defined in the
[declarative configuration specification](https://github.com/open-telemetry/opentelemetry-configuration).
If `file_format` is missing, declarative configuration is not applied.

### Example: Exporters and Sampler Rules (Spring Style)

Below is a simplified view of the configuration used in this module. All keys are indented under
`otel:` as required by Spring Boot declarative configuration. Refer to the actual
[`application.yaml`](./src/main/resources/application.yaml) for the complete version.

```yaml
otel:
file_format: "1.0-rc.2"

tracer_provider:
sampler:
rule_based_routing:
fallback_sampler:
always_on:
span_kind: SERVER
rules:
- action: DROP
attribute: url.path
pattern: /actuator.*
```

This configuration:
- Uses the `rule_based_routing` sampler from the OpenTelemetry contrib extension
- Excludes health check endpoints (`/actuator.*`) from tracing using the `DROP` action
- Samples all other requests using the `always_on` fallback sampler
- Only applies to `SERVER` span kinds

## Spring Boot Starter–Specific Notes

### Spring Boot Starter Version

- Declarative configuration is supported by the OpenTelemetry Spring Boot Starter starting with
version **2.22.0**
- Ensure your dependencies use at least this version; otherwise, `file_format` and other declarative
config features may be ignored

### Property Metadata and IDE Auto-Completion

Most IDEs derive auto-completion for Spring properties from Spring Boot configuration metadata. At
the time of this example, that metadata is primarily based on the **non-declarative** configuration
schema.

As a result:

- Auto-suggested properties in IDEs may be incomplete or incorrect for declarative configuration
under `otel:`
- Some declarative configuration keys may not appear in auto-completion at all

When in doubt:

- Prefer the official schema and examples in
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
- Then adapt those examples by nesting them under the `otel:` root in your `application.yaml`

### Placeholder Default Values: `:` vs `:-`

Spring Boot’s property placeholder syntax differs slightly from generic examples you might see in
OpenTelemetry docs.

- Generic examples sometimes use `${VAR_NAME:-default}` for default values
- **Spring Boot uses `:` instead of `:-`**

For example, in this module we configure the OTLP HTTP trace endpoint as:

```yaml
otel:
tracer_provider:
processors:
- batch:
exporter:
otlp_http:
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}/v1/traces
```

Here, `http://localhost:4318` is used as the default if the `OTEL_EXPORTER_OTLP_ENDPOINT`
environment variable is not set.

When copying configuration from non-Spring examples, always convert `:-` to `:` in placeholders.

## Declarative vs Programmatic Configuration

Declarative configuration, as used in this example, allows you to express routing and sampling rules
entirely in configuration files. This is ideal for:

- Operational teams that need to adjust sampling or filtering without changing code
- Environments where configuration is managed externally (Kubernetes ConfigMaps, Spring Cloud
Config, etc.)

For more advanced or dynamic scenarios, you can still use **programmatic** configuration. The
`spring-native` module in
this repository contains an example of this:

- See `configureSampler` in
[`OpenTelemetryConfig`](../spring-native/src/main/java/io/opentelemetry/example/graal/OpenTelemetryConfig.java)
- It uses `RuleBasedRoutingSampler` programmatically to drop spans for actuator endpoints
(`/actuator*`), replicating the behavior we achieve declaratively via YAML in this module

In many cases, you can start with declarative configuration (as in this module) and only fall back
to programmatic customization for highly dynamic or application-specific logic.

## Troubleshooting and Tips

If the behavior is not what you expect, here are a few things to check:

- **Health checks are still traced**
- Verify the `rules` section under `otel.tracer_provider.sampler.rule_based_routing` in
`application.yaml`
- Ensure the `pattern` matches your actual actuator paths (e.g., `/actuator.*`)
- Confirm that `span_kind` is set to `SERVER` (or another correct span kind for your traffic)

- **No spans are exported**
- Confirm that `otel.file_format` is set correctly (for example, `"1.0-rc.2"`)
- Check that at least one exporter is configured (e.g., `otlp_http` or `console`)
- Look for startup warnings or errors related to OpenTelemetry configuration

- **Properties seem to be ignored**
- Make sure you are modifying the correct `application.yaml` for the active Spring profile
- Verify that all configuration keys are indented correctly under the `otel:` root
- Double-check that any placeholders use `:` for defaults (e.g.,
`${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}`)

If issues persist, compare your configuration to:

- This module’s [`application.yaml`](./src/main/resources/application.yaml)
- The Java Agent example in [`javaagent-declarative-configuration`](../javaagent-declarative-configuration)
- The reference schemas and examples in
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)

## Follow-up: applying the same pattern to Spring Native

As a follow-up, you can reuse the ideas from this module to extend the `spring-native` example
with declarative configuration and a Collector running via Docker Compose:

- Start from the `spring-native` module in this repository.
- delete the `application.properties` file in `spring-native/src/main/resources/`
- copy `application.yaml` from this module to `spring-native/src/main/resources/`
- add the settings from `application.properties` back into that `application.yaml`
- follow the instructions in [spring-native/README.md](../spring-native/README.md) to run with
Docker Compose

Note that `OpenTelemetryConfig.otelCustomizer` is being ignored in this case, since we are using
declarative configuration now, which uses `DeclarativeConfigurationCustomizerProvider` instead of
`AutoConfigurationCustomizerProvider`.

```shell
rm ../spring-native/src/main/resources/application.properties
cp src/main/resources/application.yaml ../spring-native/src/main/resources/
echo "spring:
datasource:
url: jdbc:h2:mem:db
management:
endpoints:
web:
exposure:
include: '*'" >> ../spring-native/src/main/resources/application.yaml
```
29 changes: 29 additions & 0 deletions spring-declarative-configuration/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import org.gradle.kotlin.dsl.named
import org.springframework.boot.gradle.plugin.SpringBootPlugin
import org.springframework.boot.gradle.tasks.bundling.BootJar

plugins {
id("java")
id("org.springframework.boot") version "3.5.7"
}

description = "OpenTelemetry Example for Spring Boot with Declarative Configuration"

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

dependencies {
implementation(platform(SpringBootPlugin.BOM_COORDINATES))
implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.22.0"))
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter")
}

tasks.named<BootJar>("bootJar") {
archiveFileName = "spring-declarative-configuration.jar"
}

8 changes: 8 additions & 0 deletions spring-declarative-configuration/oats/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM eclipse-temurin:21.0.9_10-jre@sha256:4332b7939ba5b7fabde48f4da21ebe45a4f8943d5b3319720c321ac577e65fb1

WORKDIR /usr/src/app/

ADD ./build/libs/spring-declarative-configuration.jar ./app.jar

EXPOSE 8080
ENTRYPOINT [ "java", "-jar", "./app.jar" ]
15 changes: 15 additions & 0 deletions spring-declarative-configuration/oats/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '3'
services:
app:
build:
context: ../
dockerfile: oats/Dockerfile
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4318
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 10s
timeout: 5s
retries: 3
Loading