diff --git a/.github/scripts/run-oats-tests.sh b/.github/scripts/run-oats-tests.sh index e3adbb600..5f286b445 100755 --- a/.github/scripts/run-oats-tests.sh +++ b/.github/scripts/run-oats-tests.sh @@ -6,7 +6,12 @@ pushd logging-k8s-stdout-otlp-json ../gradlew assemble popd +pushd javaagent-declarative-configuration +../gradlew bootJar +popd + wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash go install github.com/grafana/oats@v0.3.1 oats -timeout 5m logging-k8s-stdout-otlp-json/ +oats -timeout 5m javaagent-declarative-configuration/oats/ diff --git a/.github/workflows/oats-tests.yml b/.github/workflows/oats-tests.yml index ed54c0eb6..0c789d01c 100644 --- a/.github/workflows/oats-tests.yml +++ b/.github/workflows/oats-tests.yml @@ -7,6 +7,7 @@ on: paths: - .github/workflows/oats-tests.yml - 'logging-k8s-stdout-otlp-json/**' + - 'javaagent-declarative-configuration/oats/**' workflow_dispatch: permissions: diff --git a/javaagent-declarative-configuration/.gitignore b/javaagent-declarative-configuration/.gitignore new file mode 100644 index 000000000..363af2af7 --- /dev/null +++ b/javaagent-declarative-configuration/.gitignore @@ -0,0 +1 @@ +opentelemetry-javaagent.jar \ No newline at end of file diff --git a/javaagent-declarative-configuration/README.md b/javaagent-declarative-configuration/README.md new file mode 100644 index 000000000..1cd1c1d69 --- /dev/null +++ b/javaagent-declarative-configuration/README.md @@ -0,0 +1,80 @@ +# Java Agent Declarative Configuration Example + +This example demonstrates how to use [declarative configuration](https://opentelemetry.io/docs/specs/otel/configuration/#declarative-configuration) with the OpenTelemetry Java Agent to configure tracing behavior. + +The configuration file is located at [otel-agent-config.yaml](./otel-agent-config.yaml). + +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 Java Agent JAR file (see next step) + +Download the OpenTelemetry Java Agent: +```bash +# Download the latest OpenTelemetry Java Agent +curl -L -o opentelemetry-javaagent.jar https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar +``` + +### Step 1: Build the Application + +```bash +# Build the JAR - Run from the javaagent-declarative-configuration directory +../gradlew bootJar +``` + +### Step 2: Run with OpenTelemetry Java Agent + +```bash +# From the javaagent-declarative-configuration directory + +# Run with the OpenTelemetry Java Agent and contrib extension +java -javaagent:opentelemetry-javaagent.jar \ + -Dotel.experimental.config.file=$(pwd)/otel-agent-config.yaml \ + -jar build/libs/javaagent-declarative-configuration.jar +``` + +### Step 3: Test the Endpoints + +Open a new terminal and test both endpoints: + +```bash +# This endpoint will NOT be traced (excluded by configuration) +curl http://localhost:8080/actuator/health + +# This endpoint WILL be traced normally +curl http://localhost:8080/api/example +``` + +### Step 4: Verify Tracing Behavior + +Check the application logs to see: +- Health check requests (`/actuator/health`) should NOT generate traces (excluded by configuration) +- API requests (`/api/example`) should generate traces with console output + +## Configuration + +The `otel-agent-config.yaml` file demonstrates rule-based sampling using declarative configuration to exclude health checks from tracing: + +```yaml +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 diff --git a/javaagent-declarative-configuration/build.gradle.kts b/javaagent-declarative-configuration/build.gradle.kts new file mode 100644 index 000000000..c11f9c589 --- /dev/null +++ b/javaagent-declarative-configuration/build.gradle.kts @@ -0,0 +1,20 @@ +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.6" +} + +description = "OpenTelemetry Java Agent Declarative Configuration Example" +val moduleName by extra { "io.opentelemetry.examples.javaagent.declarative" } + +dependencies { + implementation(platform(SpringBootPlugin.BOM_COORDINATES)) + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-actuator") +} + +tasks.named("bootJar") { + archiveFileName = "javaagent-declarative-configuration.jar" +} diff --git a/javaagent-declarative-configuration/oats/Dockerfile b/javaagent-declarative-configuration/oats/Dockerfile new file mode 100644 index 000000000..03ac989ae --- /dev/null +++ b/javaagent-declarative-configuration/oats/Dockerfile @@ -0,0 +1,15 @@ +FROM eclipse-temurin:21.0.8_9-jre@sha256:615497e30ae2b2654ff7bccc7cb057c27443041994593c726852f04dc99830b1 + +WORKDIR /usr/src/app/ + +# renovate: datasource=github-releases depName=opentelemetry-java-instrumentation packageName=open-telemetry/opentelemetry-java-instrumentation +ENV OPENTELEMETRY_JAVA_INSTRUMENTATION_VERSION=v2.21.0 + +ADD ./build/libs/javaagent-declarative-configuration.jar ./app.jar +ADD --chmod=644 https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$OPENTELEMETRY_JAVA_INSTRUMENTATION_VERSION/opentelemetry-javaagent.jar ./opentelemetry-javaagent.jar +ADD ./otel-agent-config.yaml ./otel-agent-config.yaml +ENV JAVA_TOOL_OPTIONS=-javaagent:./opentelemetry-javaagent.jar +ENV OTEL_EXPERIMENTAL_CONFIG_FILE=/usr/src/app/otel-agent-config.yaml + +EXPOSE 8080 +ENTRYPOINT [ "java", "-jar", "./app.jar" ] \ No newline at end of file diff --git a/javaagent-declarative-configuration/oats/docker-compose.yml b/javaagent-declarative-configuration/oats/docker-compose.yml new file mode 100644 index 000000000..b43478e7d --- /dev/null +++ b/javaagent-declarative-configuration/oats/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + app: + build: + context: ../ + dockerfile: oats/Dockerfile + environment: + OTEL_SERVICE_NAME: "declarative-config-example-app" + OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4318 + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://lgtm:4318 + OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf + ports: + - "8080:8080" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] + interval: 10s + timeout: 5s + retries: 3 \ No newline at end of file diff --git a/javaagent-declarative-configuration/oats/oats.yaml b/javaagent-declarative-configuration/oats/oats.yaml new file mode 100644 index 000000000..3be11e39e --- /dev/null +++ b/javaagent-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/javaagent-declarative-configuration/otel-agent-config.yaml b/javaagent-declarative-configuration/otel-agent-config.yaml new file mode 100644 index 000000000..9e2392039 --- /dev/null +++ b/javaagent-declarative-configuration/otel-agent-config.yaml @@ -0,0 +1,55 @@ +# See https://github.com/open-telemetry/opentelemetry-configuration for details on schema and examples + +file_format: "1.0-rc.2" + +resource: + attributes: + - name: service.name + value: spring-boot-declarative-config-example + +propagator: + composite: + - tracecontext: + - baggage: + +tracer_provider: + processors: + - batch: + exporter: + otlp_http: + endpoint: ${OTEL_EXPORTER_OTLP_TRACES_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: + endpoint: ${OTEL_EXPORTER_OTLP_METRICS_ENDPOINT:-http://localhost:4318}/v1/metrics + +logger_provider: + processors: + - batch: + exporter: + otlp_http: + endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/logs diff --git a/javaagent-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/ApiController.java b/javaagent-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/ApiController.java new file mode 100644 index 000000000..e9c3d43c1 --- /dev/null +++ b/javaagent-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/javaagent-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/Application.java b/javaagent-declarative-configuration/src/main/java/io/opentelemetry/examples/fileconfig/Application.java new file mode 100644 index 000000000..7987bb5c7 --- /dev/null +++ b/javaagent-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/javaagent/build.gradle.kts b/javaagent/build.gradle.kts index a18112f80..e0086cb33 100644 --- a/javaagent/build.gradle.kts +++ b/javaagent/build.gradle.kts @@ -16,7 +16,6 @@ java { } val agent = configurations.create("agent") -val extension = configurations.create("extension") dependencies { implementation(platform(SpringBootPlugin.BOM_COORDINATES)) @@ -27,9 +26,6 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") agent("io.opentelemetry.javaagent:opentelemetry-javaagent:2.21.0") - extension("io.opentelemetry.contrib:opentelemetry-samplers:1.50.0-alpha") { - isTransitive = false - } } val copyAgent = tasks.register("copyAgent") { @@ -38,15 +34,9 @@ val copyAgent = tasks.register("copyAgent") { rename("opentelemetry-javaagent-.*\\.jar", "opentelemetry-javaagent.jar") } -val copyExtension = tasks.register("copyExtension") { - from(extension.singleFile) - into(layout.buildDirectory.dir("agent")) - rename(".*\\.jar", "opentelemetry-javaagent-extension.jar") -} tasks.named("bootJar") { dependsOn(copyAgent) - dependsOn(copyExtension) archiveFileName = "app.jar" } diff --git a/javaagent/sdk-config.yaml b/javaagent/sdk-config.yaml index d2f1826d2..253c37f64 100644 --- a/javaagent/sdk-config.yaml +++ b/javaagent/sdk-config.yaml @@ -1,4 +1,4 @@ -# Copy of https://github.com/open-telemetry/opentelemetry-configuration/blob/v0.3.0/examples/sdk-config.yaml +# Copy of https://github.com/open-telemetry/opentelemetry-configuration/blob/v1.0.0-rc.2/examples/sdk-config.yaml # with the following changes: # - OpenTelemetry Java Agent properties added at .instrumentation.java. # - OTLP exporter endpoints modified to point to http://collector:4318/v1/{path} to export to collector from docker-compose.yml @@ -13,7 +13,7 @@ # vars defined in https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/. # The file format version. -file_format: "0.3" +file_format: "1.0-rc.2" # Configure if the SDK is disabled or not. This is not required to be provided to ensure the SDK isn't disabled, the default value when this is not provided is for the SDK to be enabled. disabled: false @@ -36,7 +36,9 @@ attribute_limits: # Configure text map context propagators. propagator: # Configure the set of propagators to include in the composite text map propagator. - composite: [ tracecontext, baggage ] + composite: + - tracecontext: + - baggage: # Configure tracer provider. tracer_provider: diff --git a/settings.gradle.kts b/settings.gradle.kts index 2b40ba343..790da7535 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,7 @@ rootProject.name = "opentelemetry-java-examples" include( ":opentelemetry-examples-autoconfigure", ":opentelemetry-examples-declarative-configuration", + ":opentelemetry-examples-javaagent-declarative-configuration", ":opentelemetry-examples-http", ":opentelemetry-examples-jaeger", ":opentelemetry-examples-javaagent",