Skip to content

Commit 166dc3f

Browse files
authored
add Spring declarative config example (#911)
1 parent d3e56be commit 166dc3f

File tree

14 files changed

+446
-12
lines changed

14 files changed

+446
-12
lines changed

.github/renovate.json5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"matchPackageNames": ["org.springframework.boot:**"],
4040
"matchFileNames": [
4141
"doc-snippets/spring-starter/build.gradle.kts",
42+
"spring-declarative-configuration/build.gradle.kts",
4243
"spring-native/build.gradle.kts"
4344
],
4445
"matchUpdateTypes": ["major"],

.github/workflows/oats-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ on:
1010
- .mise/tasks/oats-tests.sh
1111
- 'logging-k8s-stdout-otlp-json/**'
1212
- 'javaagent-declarative-configuration/**'
13+
- 'spring-declarative-configuration/**'
1314
- 'doc-snippets/extensions-minimal/**'
1415
workflow_dispatch:
1516

.mise/tasks/oats-tests.sh

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44
set -euo pipefail
55

66
# Java is not managed by mise, but any java 17+ installation should work
7-
pushd logging-k8s-stdout-otlp-json
8-
../gradlew clean assemble
9-
popd
7+
./gradlew \
8+
:doc-snippets:extensions-testapp:jar :doc-snippets:extensions-minimal:shadowJar \
9+
:opentelemetry-examples-javaagent-declarative-configuration:bootJar \
10+
:opentelemetry-examples-logging-k8s-stdout-otlp-json:assemble \
11+
:opentelemetry-examples-spring-declarative-configuration:bootJar
1012

11-
pushd javaagent-declarative-configuration
12-
../gradlew clean bootJar
13-
popd
14-
15-
./gradlew :doc-snippets:extensions-testapp:jar :doc-snippets:extensions-minimal:shadowJar
16-
17-
oats -timeout 5m logging-k8s-stdout-otlp-json/
18-
oats -timeout 5m javaagent-declarative-configuration/oats/
1913
oats -timeout 5m doc-snippets/extensions-minimal/oats/
14+
oats -timeout 5m javaagent-declarative-configuration/oats/
15+
oats -timeout 5m logging-k8s-stdout-otlp-json/
16+
oats -timeout 5m spring-declarative-configuration/oats/

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ To build the all of examples, run:
9191
OpenTelemetry Java Agent to configure tracing behavior, including
9292
excluding specific endpoints from tracing.
9393
- Note: This example requires Java 17 or higher.
94+
- [Declarative Configuration with the OpenTelemetry Spring Boot Starter](spring-declarative-configuration)
95+
- This module demonstrates how to use declarative configuration with the
96+
OpenTelemetry Spring Boot Starter to configure tracing behavior,
97+
including excluding specific endpoints from tracing.
98+
- Note: This example requires Java 17 or higher.
9499

95100
## Contributing
96101

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ include(
7171
":opentelemetry-examples-telemetry-testing",
7272
":opentelemetry-examples-zipkin",
7373
":opentelemetry-examples-spring-native",
74+
":opentelemetry-examples-spring-declarative-configuration",
7475
":opentelemetry-examples-kotlin-extension",
7576
":opentelemetry-examples-grpc",
7677
":opentelemetry-examples-resource-detection-gcp",
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# Spring Boot Declarative Configuration Example
2+
3+
This example demonstrates how to
4+
use [declarative configuration](https://opentelemetry.io/docs/specs/otel/configuration/#declarative-configuration)
5+
with the OpenTelemetry Spring Boot Starter to configure tracing, metrics, and logging for a Spring
6+
Boot application.
7+
8+
Instead of using the OpenTelemetry Java Agent, this module uses the
9+
[OpenTelemetry Spring Boot Starter](https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/)
10+
and configures declarative behavior via standard Spring Boot configuration in `application.yaml`.
11+
12+
The main configuration file for this example is:
13+
14+
- [`src/main/resources/application.yaml`](./src/main/resources/application.yaml)
15+
16+
For the underlying declarative configuration schema and additional examples, see the
17+
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
18+
repository.
19+
Remember that when you copy examples from that repository into a Spring Boot app, you must nest them
20+
under the `otel:` root node (two-space indentation for all keys shown below).
21+
22+
This Spring Boot application includes two endpoints:
23+
24+
- `/actuator/health` – A health check endpoint (from Spring Boot Actuator) that is configured to be
25+
**excluded** from tracing
26+
- `/api/example` – A simple API endpoint that **will be traced** normally
27+
28+
## End-to-End Instructions
29+
30+
### Prerequisites
31+
32+
- Java 17 or higher
33+
- OpenTelemetry Spring Boot Starter **2.22.0+** on the classpath of this application (already
34+
configured in this example’s [build file](./build.gradle.kts))
35+
36+
### Step 1: Build the Application
37+
38+
From this `spring-declarative-configuration` directory:
39+
40+
```bash
41+
../gradlew bootJar
42+
```
43+
44+
This builds the Spring Boot fat JAR for the example application.
45+
46+
### Step 2: Run the Spring Boot Application
47+
48+
Run the application as a normal Spring Boot JAR – no `-javaagent` flag and no separate
49+
`otel-agent-config.yaml` file
50+
are needed. Declarative configuration is picked up from `application.yaml` by the Spring Boot
51+
Starter.
52+
53+
```bash
54+
java -jar build/libs/spring-declarative-configuration.jar
55+
```
56+
57+
The Spring Boot Starter will automatically:
58+
59+
- Initialize OpenTelemetry SDK
60+
- Read declarative configuration under the `otel:` root in `application.yaml`
61+
- Apply exporters, processors, and sampler rules defined there
62+
63+
### Step 3: Test the Endpoints
64+
65+
In a separate terminal, call both endpoints:
66+
67+
```bash
68+
# This endpoint will NOT be traced (excluded by declarative configuration)
69+
curl http://localhost:8080/actuator/health
70+
71+
# This endpoint WILL be traced normally
72+
curl http://localhost:8080/api/example
73+
```
74+
75+
### Step 4: Verify Tracing, Metrics, and Logs
76+
77+
By default, this example configures:
78+
79+
- An OTLP HTTP exporter for traces, metrics, and logs
80+
- A console exporter for traces for easy local inspection
81+
82+
Check the application logs to see that:
83+
84+
- Health check requests (`/actuator/health`) **do not** generate spans (dropped by sampler rules)
85+
- Requests to `/api/example` **do** generate spans and are exported to console and/or OTLP
86+
87+
If you have an OTLP-compatible backend (e.g., the OpenTelemetry Collector, Jaeger, Tempo, etc.)
88+
listening on
89+
`http://localhost:4318`, you can inspect the exported telemetry there as well.
90+
91+
## Declarative Configuration with Spring Boot
92+
93+
The declarative configuration used by this example lives in [`application.yaml`](./src/main/resources/application.yaml)
94+
under the `otel:` root key.
95+
96+
```yaml
97+
otel:
98+
# ... see src/main/resources/application.yaml for the full configuration
99+
```
100+
101+
This layout follows the declarative configuration schema defined in the
102+
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
103+
repository, but adapted for Spring Boot:
104+
105+
- All OpenTelemetry configuration keys live under the `otel:` root
106+
- Configuration blocks from the reference repo (such as `tracer_provider`, `meter_provider`,
107+
`logger_provider`, etc.) are indented by **two spaces** beneath `otel:`
108+
- The configuration is loaded via the OpenTelemetry Spring Boot Starter instead of the Java Agent
109+
110+
### Opting In with `file_format`
111+
112+
Declarative configuration is **opt-in**. In this Spring Boot example, you enable declarative
113+
configuration by setting `file_format` under `otel:` in `application.yaml`:
114+
115+
```yaml
116+
otel:
117+
file_format: "1.0-rc.2"
118+
# ... other configuration
119+
```
120+
121+
The `file_format` value follows the versions defined in the
122+
[declarative configuration specification](https://github.com/open-telemetry/opentelemetry-configuration).
123+
If `file_format` is missing, declarative configuration is not applied.
124+
125+
### Example: Exporters and Sampler Rules (Spring Style)
126+
127+
Below is a simplified view of the configuration used in this module. All keys are indented under
128+
`otel:` as required by Spring Boot declarative configuration. Refer to the actual
129+
[`application.yaml`](./src/main/resources/application.yaml) for the complete version.
130+
131+
```yaml
132+
otel:
133+
file_format: "1.0-rc.2"
134+
135+
tracer_provider:
136+
sampler:
137+
rule_based_routing:
138+
fallback_sampler:
139+
always_on:
140+
span_kind: SERVER
141+
rules:
142+
- action: DROP
143+
attribute: url.path
144+
pattern: /actuator.*
145+
```
146+
147+
This configuration:
148+
149+
- Uses the `rule_based_routing` sampler from the OpenTelemetry contrib extension
150+
- Excludes health check endpoints (`/actuator.*`) from tracing using the `DROP` action
151+
- Samples all other requests using the `always_on` fallback sampler
152+
- Only applies to `SERVER` span kinds
153+
154+
## Spring Boot Starter–Specific Notes
155+
156+
### Spring Boot Starter Version
157+
158+
- Declarative configuration is supported by the OpenTelemetry Spring Boot Starter starting with
159+
version **2.22.0**
160+
- Ensure your dependencies use at least this version; otherwise, `file_format` and other declarative
161+
config features may be ignored
162+
163+
### Property Metadata and IDE Auto-Completion
164+
165+
Most IDEs derive auto-completion for Spring properties from Spring Boot configuration metadata. At
166+
the time of this example, that metadata is primarily based on the **non-declarative** configuration
167+
schema.
168+
169+
As a result:
170+
171+
- Auto-suggested properties in IDEs may be incomplete or incorrect for declarative configuration
172+
under `otel:`
173+
- Some declarative configuration keys may not appear in auto-completion at all
174+
175+
When in doubt:
176+
177+
- Prefer the official schema and examples in
178+
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
179+
- Then adapt those examples by nesting them under the `otel:` root in your `application.yaml`
180+
181+
### Placeholder Default Values: `:` vs `:-`
182+
183+
Spring Boot’s property placeholder syntax differs slightly from generic examples you might see in
184+
OpenTelemetry docs.
185+
186+
- Generic examples sometimes use `${VAR_NAME:-default}` for default values
187+
- **Spring Boot uses `:` instead of `:-`**
188+
189+
For example, in this module we configure the OTLP HTTP trace endpoint as:
190+
191+
```yaml
192+
otel:
193+
tracer_provider:
194+
processors:
195+
- batch:
196+
exporter:
197+
otlp_http:
198+
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}/v1/traces
199+
```
200+
201+
Here, `http://localhost:4318` is used as the default if the `OTEL_EXPORTER_OTLP_ENDPOINT`
202+
environment variable is not set.
203+
204+
When copying configuration from non-Spring examples, always convert `:-` to `:` in placeholders.
205+
206+
## Declarative vs Programmatic Configuration
207+
208+
Declarative configuration, as used in this example, allows you to express routing and sampling rules
209+
entirely in configuration files. This is ideal for:
210+
211+
- Operational teams that need to adjust sampling or filtering without changing code
212+
- Environments where configuration is managed externally (Kubernetes ConfigMaps, Spring Cloud
213+
Config, etc.)
214+
215+
For more advanced or dynamic scenarios, you can still use **programmatic** configuration. The
216+
`spring-native` module in
217+
this repository contains an example of this:
218+
219+
- See `configureSampler` in
220+
[`OpenTelemetryConfig`](../spring-native/src/main/java/io/opentelemetry/example/graal/OpenTelemetryConfig.java)
221+
- It uses `RuleBasedRoutingSampler` programmatically to drop spans for actuator endpoints
222+
(`/actuator*`), replicating the behavior we achieve declaratively via YAML in this module
223+
224+
In many cases, you can start with declarative configuration (as in this module) and only fall back
225+
to programmatic customization for highly dynamic or application-specific logic.
226+
227+
## Troubleshooting and Tips
228+
229+
If the behavior is not what you expect, here are a few things to check:
230+
231+
- **Health checks are still traced**
232+
- Verify the `rules` section under `otel.tracer_provider.sampler.rule_based_routing` in
233+
`application.yaml`
234+
- Ensure the `pattern` matches your actual actuator paths (e.g., `/actuator.*`)
235+
- Confirm that `span_kind` is set to `SERVER` (or another correct span kind for your traffic)
236+
237+
- **No spans are exported**
238+
- Confirm that `otel.file_format` is set correctly (for example, `"1.0-rc.2"`)
239+
- Check that at least one exporter is configured (e.g., `otlp_http` or `console`)
240+
- Look for startup warnings or errors related to OpenTelemetry configuration
241+
242+
- **Properties seem to be ignored**
243+
- Make sure you are modifying the correct `application.yaml` for the active Spring profile
244+
- Verify that all configuration keys are indented correctly under the `otel:` root
245+
- Double-check that any placeholders use `:` for defaults (e.g.,
246+
`${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}`)
247+
248+
If issues persist, compare your configuration to:
249+
250+
- This module’s [`application.yaml`](./src/main/resources/application.yaml)
251+
- The Java Agent example in [`javaagent-declarative-configuration`](../javaagent-declarative-configuration)
252+
- The reference schemas and examples in
253+
[opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import org.gradle.kotlin.dsl.named
2+
import org.springframework.boot.gradle.plugin.SpringBootPlugin
3+
import org.springframework.boot.gradle.tasks.bundling.BootJar
4+
5+
plugins {
6+
id("java")
7+
id("org.springframework.boot") version "3.5.7"
8+
}
9+
10+
description = "OpenTelemetry Example for Spring Boot with Declarative Configuration"
11+
12+
java {
13+
toolchain {
14+
languageVersion.set(JavaLanguageVersion.of(17))
15+
}
16+
}
17+
18+
dependencies {
19+
implementation(platform(SpringBootPlugin.BOM_COORDINATES))
20+
implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.22.0"))
21+
implementation("org.springframework.boot:spring-boot-starter-actuator")
22+
implementation("org.springframework.boot:spring-boot-starter-web")
23+
implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter")
24+
}
25+
26+
tasks.named<BootJar>("bootJar") {
27+
archiveFileName = "spring-declarative-configuration.jar"
28+
}
29+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM eclipse-temurin:21.0.9_10-jre@sha256:4332b7939ba5b7fabde48f4da21ebe45a4f8943d5b3319720c321ac577e65fb1
2+
3+
WORKDIR /usr/src/app/
4+
5+
ADD ./build/libs/spring-declarative-configuration.jar ./app.jar
6+
7+
EXPOSE 8080
8+
ENTRYPOINT [ "java", "-jar", "./app.jar" ]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: '3'
2+
services:
3+
app:
4+
build:
5+
context: ../
6+
dockerfile: oats/Dockerfile
7+
environment:
8+
OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4318
9+
ports:
10+
- "8080:8080"
11+
healthcheck:
12+
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
13+
interval: 10s
14+
timeout: 5s
15+
retries: 3
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats
2+
oats-schema-version: 2
3+
4+
docker-compose:
5+
files:
6+
- ./docker-compose.yml
7+
8+
input:
9+
# This endpoint should be traced normally
10+
- path: /api/example
11+
# This endpoint should NOT be traced (excluded by declarative config)
12+
- path: /actuator/health
13+
14+
expected:
15+
traces:
16+
# Verify that /api/example creates a trace with SERVER span
17+
- traceql: '{ span.http.route = "/api/example" }'
18+
equals: "GET /api/example"
19+
attributes:
20+
http.request.method: "GET"
21+
http.route: "/api/example"
22+
- traceql: '{ span.http.route = "/actuator/health" }'
23+
count:
24+
max: 0

0 commit comments

Comments
 (0)