diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1b2281e927..8a7124746c 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -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"], diff --git a/.github/workflows/oats-tests.yml b/.github/workflows/oats-tests.yml index d8d84edb96..598a16351a 100644 --- a/.github/workflows/oats-tests.yml +++ b/.github/workflows/oats-tests.yml @@ -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: diff --git a/.mise/tasks/oats-tests.sh b/.mise/tasks/oats-tests.sh index 9b73e820d3..5a0507a246 100755 --- a/.mise/tasks/oats-tests.sh +++ b/.mise/tasks/oats-tests.sh @@ -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 . diff --git a/README.md b/README.md index 7a35020f0b..e44a7ed91c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts index cd6cd2eca4..bd0126598b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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", diff --git a/spring-declarative-configuration/README.md b/spring-declarative-configuration/README.md new file mode 100644 index 0000000000..4642a625d7 --- /dev/null +++ b/spring-declarative-configuration/README.md @@ -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 +``` diff --git a/spring-declarative-configuration/build.gradle.kts b/spring-declarative-configuration/build.gradle.kts new file mode 100644 index 0000000000..7d3529034e --- /dev/null +++ b/spring-declarative-configuration/build.gradle.kts @@ -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") { + archiveFileName = "spring-declarative-configuration.jar" +} + diff --git a/spring-declarative-configuration/oats/Dockerfile b/spring-declarative-configuration/oats/Dockerfile new file mode 100644 index 0000000000..aa69c95f7d --- /dev/null +++ b/spring-declarative-configuration/oats/Dockerfile @@ -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" ] diff --git a/spring-declarative-configuration/oats/docker-compose.yml b/spring-declarative-configuration/oats/docker-compose.yml new file mode 100644 index 0000000000..2191ba601f --- /dev/null +++ b/spring-declarative-configuration/oats/docker-compose.yml @@ -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 diff --git a/spring-declarative-configuration/oats/oats.yaml b/spring-declarative-configuration/oats/oats.yaml new file mode 100644 index 0000000000..3be11e39ed --- /dev/null +++ b/spring-declarative-configuration/oats/oats.yaml @@ -0,0 +1,26 @@ +# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats + +docker-compose: + files: + - ./docker-compose.yml + app-service: app + app-docker-tag: javaagent-declarative-config:latest + app-docker-port: 8080 + +input: + # This endpoint should be traced normally + - path: /api/example + # This endpoint should NOT be traced (excluded by declarative config) + # We send the request but don't assert spans for it - the absence of spans + # for /actuator/health demonstrates the sampling rule is working + - path: /actuator/health + +expected: + traces: + # Verify that /api/example creates a trace with SERVER span + - traceql: '{ span.http.route = "/api/example" }' + spans: + - name: "GET /api/example" + attributes: + http.request.method: "GET" + http.route: "/api/example" \ No newline at end of file diff --git a/spring-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/ApiController.java b/spring-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/ApiController.java new file mode 100644 index 0000000000..e9c3d43c15 --- /dev/null +++ b/spring-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/ApiController.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.examples.fileconfig; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +public class ApiController { + + @GetMapping("/example") + public ResponseEntity example() { + return ResponseEntity.ok("Hello from OpenTelemetry example API!"); + } +} diff --git a/spring-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/Application.java b/spring-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/Application.java new file mode 100644 index 0000000000..7987bb5c71 --- /dev/null +++ b/spring-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/Application.java @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.examples.fileconfig; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-declarative-configuration/src/main/resources/application.yaml b/spring-declarative-configuration/src/main/resources/application.yaml new file mode 100644 index 0000000000..c94b08843a --- /dev/null +++ b/spring-declarative-configuration/src/main/resources/application.yaml @@ -0,0 +1,62 @@ +otel: + # IMPORTANT: + # 1. The auto-suggested properties are not correct, because they are based on the non-declarative config schema. + # 2. Use https://github.com/open-telemetry/opentelemetry-configuration for details on schema and examples, but + # indent all examples by two spaces to fit under the "otel:" root node (for Spring Boot declarative config). + + file_format: "1.0-rc.2" # "file_format" serves as opt-in to declarative config + + resource: + attributes: + - name: service.name + value: spring-boot-declarative-config-example + + propagator: + composite: + - tracecontext: + - baggage: + + tracer_provider: + processors: + - batch: + exporter: + otlp_http: + # Note: Spring uses : instead of :- as separator for the default value + endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}/v1/traces + + # Configure a console exporter for exploring without a collector/backend + - batch: + exporter: + console: + + # Configure sampling to exclude health check endpoints + sampler: + rule_based_routing: + fallback_sampler: + always_on: + # Filter to spans of this span_kind. Must be one of: SERVER, CLIENT, INTERNAL, CONSUMER, PRODUCER. + span_kind: SERVER # only apply to server spans + # List of rules describing spans to drop. Spans are dropped if they match one of the rules. + rules: + # The action to take when the rule is matches. Must be of: DROP, RECORD_AND_SAMPLE. + - action: DROP + # The span attribute to match against. + attribute: url.path + # The pattern to compare the span attribute to. + pattern: /actuator.* + + meter_provider: + readers: + - periodic: + exporter: + otlp_http: + # Note: Spring uses : instead of :- as separator for the default value + endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}/v1/metrics + + logger_provider: + processors: + - batch: + exporter: + otlp_http: + # Note: Spring uses : instead of :- as separator for the default value + endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}/v1/logs diff --git a/spring-native/build.gradle.kts b/spring-native/build.gradle.kts index 467ab5359b..9666f3dd08 100644 --- a/spring-native/build.gradle.kts +++ b/spring-native/build.gradle.kts @@ -2,7 +2,7 @@ import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { id("java") - id("org.springframework.boot") version "3.5.7" + id("org.springframework.boot") version "3.5.8" id("org.graalvm.buildtools.native") version "0.11.3" } diff --git a/spring-native/docker-compose.yml b/spring-native/docker-compose.yml index 6de788142b..fb8ff56c4d 100644 --- a/spring-native/docker-compose.yml +++ b/spring-native/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.8' services: app: image: otel-native-graalvm