From 77e8e7c5ebf4c232140d051a66a50aa6e14ad3fe Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 15:03:24 -0600 Subject: [PATCH 01/13] phase 1 - adding doc site --- docs/generation/site-local.yml | 6 + docs/generation/site-remote.yml | 6 + .../ROOT/pages/_partials/nav-versioned.adoc | 7 + .../docs/antora.yml | 22 +++ .../docs/modules/ROOT/nav.adoc | 5 + .../ROOT/pages/_partials/nav-versioned.adoc | 1 + .../docs/modules/ROOT/pages/index.adoc | 32 +++++ .../docs/antora.yml | 22 +++ .../docs/modules/ROOT/nav.adoc | 5 + .../ROOT/pages/_partials/nav-versioned.adoc | 2 + .../docs/modules/ROOT/pages/index.adoc | 66 +++++++++ .../docs/modules/ROOT/pages/tracing.adoc | 133 ++++++++++++++++++ 12 files changed, 307 insertions(+) create mode 100644 servicetalk-opentelemetry-asynccontext/docs/antora.yml create mode 100644 servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc create mode 100644 servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc create mode 100644 servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc create mode 100644 servicetalk-opentelemetry-http/docs/antora.yml create mode 100644 servicetalk-opentelemetry-http/docs/modules/ROOT/nav.adoc create mode 100644 servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc create mode 100644 servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc create mode 100644 servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc diff --git a/docs/generation/site-local.yml b/docs/generation/site-local.yml index 57012152c6..428d618eae 100644 --- a/docs/generation/site-local.yml +++ b/docs/generation/site-local.yml @@ -57,6 +57,12 @@ content: - url: ../../ branches: HEAD start_path: servicetalk-traffic-resilience-http/docs + - url: ../../ + branches: HEAD + start_path: servicetalk-opentelemetry-asynccontext/docs + - url: ../../ + branches: HEAD + start_path: servicetalk-opentelemetry-http/docs asciidoc: attributes: experimental: '' diff --git a/docs/generation/site-remote.yml b/docs/generation/site-remote.yml index 36bdbe27b9..ee8547936b 100644 --- a/docs/generation/site-remote.yml +++ b/docs/generation/site-remote.yml @@ -72,6 +72,12 @@ content: branches: main tags: [0.42.59] start_path: servicetalk-traffic-resilience-http/docs + - url: https://github.com/apple/servicetalk.git + branches: main + start_path: servicetalk-opentelemetry-asynccontext/docs + - url: https://github.com/apple/servicetalk.git + branches: main + start_path: servicetalk-opentelemetry-http/docs asciidoc: attributes: experimental: '' diff --git a/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/docs/modules/ROOT/pages/_partials/nav-versioned.adoc index 55cf991bf7..4a79ad6ce9 100644 --- a/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ b/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -47,5 +47,12 @@ include::{page-version}@servicetalk-loadbalancer:ROOT:partial$nav-versioned.adoc include::{page-version}@servicetalk-traffic-resilience-http:ROOT:partial$nav-versioned.adoc[] -- + +* Observability ++ +-- +include::{page-version}@servicetalk-opentelemetry-asynccontext:ROOT:partial$nav-versioned.adoc[] +include::{page-version}@servicetalk-opentelemetry-http:ROOT:partial$nav-versioned.adoc[] +-- ++ * xref:{page-version}@servicetalk::javadoc/index.adoc[Javadoc] * xref:{page-version}@servicetalk::contributing.adoc[Contributing] diff --git a/servicetalk-opentelemetry-asynccontext/docs/antora.yml b/servicetalk-opentelemetry-asynccontext/docs/antora.yml new file mode 100644 index 0000000000..28cd034e5a --- /dev/null +++ b/servicetalk-opentelemetry-asynccontext/docs/antora.yml @@ -0,0 +1,22 @@ +# +# Copyright © 2025 Apple Inc. and the ServiceTalk project authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: servicetalk-opentelemetry-asynccontext +title: OpenTelemetry Async Context +version: SNAPSHOT +nav: + - modules/ROOT/nav.adoc + \ No newline at end of file diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc new file mode 100644 index 0000000000..8ddc690987 --- /dev/null +++ b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc @@ -0,0 +1,5 @@ +ifndef::page-version[] +:page-version: SNAPSHOT +endif::[] + +include::{page-version}@servicetalk-opentelemetry-asynccontext:ROOT:partial$nav-versioned.adoc[] diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc new file mode 100644 index 0000000000..341ae8cd88 --- /dev/null +++ b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -0,0 +1 @@ +* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] \ No newline at end of file diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000000..d80ba50037 --- /dev/null +++ b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,32 @@ +// Configure {source-root} values based on how this document is rendered: on GitHub or not +ifdef::env-github[] +:source-root: +endif::[] +ifndef::env-github[] +ifndef::source-root[:source-root: https://github.com/apple/servicetalk/blob/{page-origin-refname}] +endif::[] + += OpenTelemetry Async Context + +ServiceTalk provides OpenTelemetry integration through async context propagation that ensures OpenTelemetry context and spans are properly maintained across asynchronous boundaries. + +== Overview + +The `servicetalk-opentelemetry-asynccontext` module provides the link:{source-root}/servicetalk-opentelemetry-asynccontext/src/main/java/io/servicetalk/opentelemetry/asynccontext/OtelCapturedContextProvider.java[OtelCapturedContextProvider] which integrates OpenTelemetry's context propagation with ServiceTalk's xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[Asynchronous Context] system. + +== Key Features + +* **Automatic Context Propagation**: OpenTelemetry context is automatically captured and restored across ServiceTalk's asynchronous boundaries +* **Thread Safety**: Works correctly in ServiceTalk's multi-threaded, asynchronous execution model +* **Zero Configuration**: Automatically discovered and enabled via Java's ServiceLoader mechanism + +== Usage + +This module is typically used in conjunction with the `servicetalk-opentelemetry-http` module to provide end-to-end tracing for HTTP services and clients. + +For detailed usage examples, see the OpenTelemetry examples in the ServiceTalk examples module. + +== Related Documentation + +* xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] +* xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry HTTP Tracing] \ No newline at end of file diff --git a/servicetalk-opentelemetry-http/docs/antora.yml b/servicetalk-opentelemetry-http/docs/antora.yml new file mode 100644 index 0000000000..80be15a878 --- /dev/null +++ b/servicetalk-opentelemetry-http/docs/antora.yml @@ -0,0 +1,22 @@ +# +# Copyright © 2025 Apple Inc. and the ServiceTalk project authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: servicetalk-opentelemetry-http +title: OpenTelemetry HTTP +version: SNAPSHOT +nav: + - modules/ROOT/nav.adoc + \ No newline at end of file diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/nav.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/nav.adoc new file mode 100644 index 0000000000..5bef1e0060 --- /dev/null +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/nav.adoc @@ -0,0 +1,5 @@ +ifndef::page-version[] +:page-version: SNAPSHOT +endif::[] + +include::{page-version}@servicetalk-opentelemetry-http:ROOT:partial$nav-versioned.adoc[] diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc new file mode 100644 index 0000000000..ad35886007 --- /dev/null +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -0,0 +1,2 @@ +* xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry HTTP Tracing] +** xref:{page-version}@servicetalk-opentelemetry-http::tracing.adoc[Tracing Guide] \ No newline at end of file diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000000..4ffd21417c --- /dev/null +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,66 @@ +// Configure {source-root} values based on how this document is rendered: on GitHub or not +ifdef::env-github[] +:source-root: +endif::[] +ifndef::env-github[] +ifndef::source-root[:source-root: https://github.com/apple/servicetalk/blob/{page-origin-refname}] +endif::[] + += OpenTelemetry HTTP Tracing + +ServiceTalk provides comprehensive OpenTelemetry tracing support for HTTP clients and servers through a set of filters that automatically instrument HTTP requests and responses. + +== Overview + +The `servicetalk-opentelemetry-http` module provides HTTP tracing filters that integrate with OpenTelemetry to automatically create spans for HTTP operations, propagate trace context, and collect telemetry data. + +== Key Components + +=== Client-Side Tracing +* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpRequesterFilter.java[OpenTelemetryHttpRequesterFilter] - HTTP client tracing filter + +=== Server-Side Tracing +* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpServiceFilter.java[OpenTelemetryHttpServiceFilter] - HTTP server tracing filter + +=== Configuration +* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryFilterBuilder.java[OpenTelemetryFilterBuilder] - Base builder for configuring tracing options + +== Key Features + +* **Automatic Span Creation**: Creates spans for HTTP requests and responses following OpenTelemetry semantic conventions +* **Context Propagation**: Propagates trace context across HTTP boundaries using standard headers +* **Configurable Attributes**: Capture custom request and response headers as span attributes +* **Metrics Support**: Optional collection of HTTP operation metrics +* **gRPC Support**: Specialized support for gRPC over HTTP/2 tracing +* **Filter Ordering**: Proper integration with ServiceTalk's filter chain for correct context handling + +== Quick Start + +=== Client Configuration +```java +HttpClient client = HttpClients.forSingleAddress("example.com", 80) + .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() + .componentName("my-client") + .build()) + .build(); +``` + +=== Server Configuration +```java +HttpServerBuilder.forAddress(localAddress(0)) + .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() + .build()) + .listen(service); +``` + +== Dependencies + +This module requires: +* `servicetalk-opentelemetry-asynccontext` for async context propagation +* OpenTelemetry Java libraries for span creation and context management + +== Related Documentation + +* xref:{page-version}@servicetalk-opentelemetry-http::tracing.adoc[Detailed Tracing Guide] +* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] +* xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] \ No newline at end of file diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc new file mode 100644 index 0000000000..8e772be287 --- /dev/null +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc @@ -0,0 +1,133 @@ +// Configure {source-root} values based on how this document is rendered: on GitHub or not +ifdef::env-github[] +:source-root: +endif::[] +ifndef::env-github[] +ifndef::source-root[:source-root: https://github.com/apple/servicetalk/blob/{page-origin-refname}] +endif::[] + += OpenTelemetry HTTP Tracing Guide + +This guide provides detailed information on using OpenTelemetry HTTP tracing filters with ServiceTalk. + +== Quick Start + +=== Prerequisites + +Before using OpenTelemetry tracing, ensure you have: + +* OpenTelemetry Java libraries in your classpath +* `servicetalk-opentelemetry-asynccontext` module for context propagation +* `servicetalk-opentelemetry-http` module for HTTP tracing filters + +=== Basic Client Setup + +[source,java] +---- +// Example client configuration +HttpClient client = HttpClients.forSingleAddress("example.com", 80) + .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() + .componentName("my-client") + .build()) + .build(); +---- + +=== Basic Server Setup + +[source,java] +---- +// Example server configuration +HttpServerBuilder.forAddress(localAddress(0)) + .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() + .build()) + .listen(service); +---- + +== Advanced Configuration + +=== Capturing Custom Headers + +[source,java] +---- +// Capture specific request and response headers as span attributes +OpenTelemetryHttpRequesterFilter filter = new OpenTelemetryHttpRequesterFilter.Builder() + .componentName("my-client") + .capturedRequestHeaders(Arrays.asList("x-custom-header", "authorization")) + .capturedResponseHeaders(Arrays.asList("x-response-id", "content-type")) + .build(); +---- + +=== Enabling Metrics + +[source,java] +---- +// Enable OpenTelemetry metrics collection +OpenTelemetryHttpServiceFilter filter = new OpenTelemetryHttpServiceFilter.Builder() + .enableMetrics(true) + .build(); +---- + +== Filter Ordering Guidelines + +=== Client Filters +When using multiple filters on clients, place the OpenTelemetry filter appropriately in the chain: + +[source,java] +---- +HttpClient client = HttpClients.forSingleAddress("example.com", 80) + .appendClientFilter(loggingFilter) // Before tracing for proper context + .appendClientFilter(openTelemetryFilter) // Tracing filter + .appendClientFilter(retryFilter) // After tracing + .build(); +---- + +=== Server Filters +For servers, use non-offloading filters for proper context propagation: + +[source,java] +---- +HttpServerBuilder.forAddress(localAddress(0)) + .appendNonOffloadingServiceFilter(openTelemetryFilter) // First for context + .appendNonOffloadingServiceFilter(autoDrainingFilter) // After tracing + .appendServiceFilter(exceptionMapperFilter) // After tracing + .listen(service); +---- + +== Context Propagation + +OpenTelemetry context is automatically propagated through: + +* HTTP headers using standard OpenTelemetry propagation format +* ServiceTalk's async context system via `servicetalk-opentelemetry-asynccontext` +* Thread boundaries during async operations + +== gRPC Support + +The HTTP tracing filters provide specialized support for gRPC over HTTP/2: + +* Automatic detection of gRPC requests +* gRPC-specific span naming and attributes +* Proper status code mapping + +== Troubleshooting + +=== Common Issues + +**Context Not Propagating** +Ensure `servicetalk-opentelemetry-asynccontext` is on the classpath and the filter is properly ordered. + +**Missing Spans** +Verify OpenTelemetry is properly configured and the global OpenTelemetry instance is set. + +**Performance Impact** +Consider disabling metrics if not needed, as they add overhead to request processing. + +== Examples + +For complete working examples, see the OpenTelemetry examples in the ServiceTalk examples module. + +== Related Documentation + +* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] +* xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] +* https://opentelemetry.io/docs/instrumentation/java/[OpenTelemetry Java Documentation] \ No newline at end of file From 89b1fd761d04b1adde7b27841254e76fbc2367df Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 15:35:39 -0600 Subject: [PATCH 02/13] Phase 2: add an example --- .../ROOT/pages/_partials/nav-versioned.adoc | 1 + .../docs/modules/ROOT/pages/http/index.adoc | 15 ++++ .../http/opentelemetry/build.gradle | 34 +++++++++ .../http/opentelemetry/gradle.lockfile | 56 ++++++++++++++ .../opentelemetry/OpenTelemetryClient.java | 73 +++++++++++++++++++ .../opentelemetry/OpenTelemetryServer.java | 72 ++++++++++++++++++ .../http/opentelemetry/package-info.java | 22 ++++++ .../src/main/resources/log4j2.xml | 29 ++++++++ .../docs/antora.yml | 1 - .../ROOT/pages/_partials/nav-versioned.adoc | 2 +- .../docs/modules/ROOT/pages/index.adoc | 2 +- .../docs/antora.yml | 1 - .../ROOT/pages/_partials/nav-versioned.adoc | 2 +- .../docs/modules/ROOT/pages/index.adoc | 4 +- .../docs/modules/ROOT/pages/tracing.adoc | 2 +- settings.gradle | 1 + 16 files changed, 309 insertions(+), 8 deletions(-) create mode 100644 servicetalk-examples/http/opentelemetry/build.gradle create mode 100644 servicetalk-examples/http/opentelemetry/gradle.lockfile create mode 100644 servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java create mode 100644 servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java create mode 100644 servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/package-info.java create mode 100644 servicetalk-examples/http/opentelemetry/src/main/resources/log4j2.xml diff --git a/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc index f7604e5fe5..274e04253b 100644 --- a/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ b/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -14,6 +14,7 @@ ** xref:{page-version}@servicetalk-examples::http/index.adoc#Observer[Observer] ** xref:{page-version}@servicetalk-examples::http/index.adoc#LoadBalancer[LoadBalancer] ** xref:{page-version}@servicetalk-examples::http/index.adoc#OpenTracing[OpenTracing] +** xref:{page-version}@servicetalk-examples::http/index.adoc#OpenTelemetry[OpenTelemetry] ** xref:{page-version}@servicetalk-examples::http/index.adoc#Redirects[Redirects] ** xref:{page-version}@servicetalk-examples::http/index.adoc#Retries[Retries] ** xref:{page-version}@servicetalk-examples::http/index.adoc#uds[Unix Domain Sockets] diff --git a/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc b/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc index 1285ab5566..a97e827378 100644 --- a/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc +++ b/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc @@ -264,6 +264,21 @@ Using the following classes: - link:{source-root}/servicetalk-examples/http/opentracing/src/main/java/io/servicetalk/examples/http/opentracing/ZipkinServerSimulator.java[ZipkinServerSimulator] - A server that simulates/mocks a Zipkin server, and logs requests to the console. - link:{source-root}/servicetalk-examples/http/opentracing/src/main/java/io/servicetalk/examples/http/opentracing/BraveTracingServer.java[BraveTracingServer] - A server that uses link:https://github.com/openzipkin-contrib/brave-opentracing[Brave OpenTracing] implementation. +[#OpenTelemetry] +== OpenTelemetry + +This example demonstrates the following: + +- automatically generate and propagate distributed tracing metadata using OpenTelemetry +- configure OpenTelemetry SDK with logging span exporter for local development +- integrate ServiceTalk HTTP filters with OpenTelemetry tracing +- proper async context propagation for span correlation + +Using the following classes: + +- link:{source-root}/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java[OpenTelemetryServer] - A server that demonstrates OpenTelemetry tracing configuration and automatic span generation for HTTP requests. +- link:{source-root}/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java[OpenTelemetryClient] - A client that demonstrates OpenTelemetry tracing configuration and automatic span generation for HTTP requests with context propagation. + [#Redirects] == Redirects diff --git a/servicetalk-examples/http/opentelemetry/build.gradle b/servicetalk-examples/http/opentelemetry/build.gradle new file mode 100644 index 0000000000..bfce6456a5 --- /dev/null +++ b/servicetalk-examples/http/opentelemetry/build.gradle @@ -0,0 +1,34 @@ +/* + * Copyright © 2025 Apple Inc. and the ServiceTalk project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: "java" +apply from: "../../gradle/idea.gradle" + +dependencies { + implementation project(":servicetalk-annotations") + implementation project(":servicetalk-http-netty") + implementation project(":servicetalk-opentelemetry-http") // OpenTelemetry HTTP client/server filters + implementation project(":servicetalk-opentelemetry-asynccontext") // OpenTelemetry async context propagation + + // OpenTelemetry Java SDK + implementation "io.opentelemetry:opentelemetry-api:$opentelemetryVersion" + implementation "io.opentelemetry:opentelemetry-sdk:$opentelemetryVersion" + implementation "io.opentelemetry:opentelemetry-exporter-logging:$opentelemetryVersion" + implementation "io.opentelemetry:opentelemetry-exporter-otlp:$opentelemetryVersion" + + implementation "org.slf4j:slf4j-api:$slf4jVersion" + runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" +} diff --git a/servicetalk-examples/http/opentelemetry/gradle.lockfile b/servicetalk-examples/http/opentelemetry/gradle.lockfile new file mode 100644 index 0000000000..e2da515d09 --- /dev/null +++ b/servicetalk-examples/http/opentelemetry/gradle.lockfile @@ -0,0 +1,56 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +com.google.code.findbugs:jsr305:3.0.2=compileClasspath,runtimeClasspath +com.squareup.okhttp3:okhttp:4.12.0=runtimeClasspath +com.squareup.okio:okio-jvm:3.6.0=runtimeClasspath +com.squareup.okio:okio:3.6.0=runtimeClasspath +io.netty:netty-bom:4.1.124.Final=runtimeClasspath +io.netty:netty-buffer:4.1.124.Final=runtimeClasspath +io.netty:netty-codec-dns:4.1.124.Final=runtimeClasspath +io.netty:netty-codec-http2:4.1.124.Final=runtimeClasspath +io.netty:netty-codec-http:4.1.124.Final=runtimeClasspath +io.netty:netty-codec:4.1.124.Final=runtimeClasspath +io.netty:netty-common:4.1.124.Final=runtimeClasspath +io.netty:netty-handler:4.1.124.Final=runtimeClasspath +io.netty:netty-resolver-dns-classes-macos:4.1.124.Final=runtimeClasspath +io.netty:netty-resolver-dns-native-macos:4.1.124.Final=runtimeClasspath +io.netty:netty-resolver-dns:4.1.124.Final=runtimeClasspath +io.netty:netty-resolver:4.1.124.Final=runtimeClasspath +io.netty:netty-tcnative-boringssl-static:2.0.72.Final=runtimeClasspath +io.netty:netty-tcnative-classes:2.0.72.Final=runtimeClasspath +io.netty:netty-transport-classes-epoll:4.1.124.Final=runtimeClasspath +io.netty:netty-transport-classes-kqueue:4.1.124.Final=runtimeClasspath +io.netty:netty-transport-native-epoll:4.1.124.Final=runtimeClasspath +io.netty:netty-transport-native-kqueue:4.1.124.Final=runtimeClasspath +io.netty:netty-transport-native-unix-common:4.1.124.Final=runtimeClasspath +io.netty:netty-transport:4.1.124.Final=runtimeClasspath +io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.14.0=runtimeClasspath +io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.14.0=runtimeClasspath +io.opentelemetry.semconv:opentelemetry-semconv:1.30.0=runtimeClasspath +io.opentelemetry:opentelemetry-api-incubator:1.48.0-alpha=runtimeClasspath +io.opentelemetry:opentelemetry-api:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-bom:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-context:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-exporter-common:1.48.0=runtimeClasspath +io.opentelemetry:opentelemetry-exporter-logging:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-exporter-otlp-common:1.48.0=runtimeClasspath +io.opentelemetry:opentelemetry-exporter-otlp:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.48.0=runtimeClasspath +io.opentelemetry:opentelemetry-sdk-common:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.48.0=runtimeClasspath +io.opentelemetry:opentelemetry-sdk-logs:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-sdk-metrics:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-sdk-trace:1.48.0=compileClasspath,runtimeClasspath +io.opentelemetry:opentelemetry-sdk:1.48.0=compileClasspath,runtimeClasspath +org.apache.logging.log4j:log4j-api:2.23.1=runtimeClasspath +org.apache.logging.log4j:log4j-core:2.23.1=runtimeClasspath +org.apache.logging.log4j:log4j-slf4j-impl:2.23.1=runtimeClasspath +org.jctools:jctools-core:4.0.3=runtimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=runtimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=runtimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=runtimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=runtimeClasspath +org.jetbrains:annotations:13.0=runtimeClasspath +org.slf4j:slf4j-api:1.7.36=compileClasspath,runtimeClasspath +empty=annotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java new file mode 100644 index 0000000000..1d5a24ec60 --- /dev/null +++ b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2025 Apple Inc. and the ServiceTalk project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.servicetalk.examples.http.opentelemetry; + +import io.servicetalk.http.api.BlockingHttpClient; +import io.servicetalk.http.api.HttpResponse; +import io.servicetalk.http.netty.HttpClients; +import io.servicetalk.opentelemetry.http.OpenTelemetryHttpRequesterFilter; + +import java.nio.charset.StandardCharsets; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; + +/** + * A client that demonstrates OpenTelemetry distributed tracing with ServiceTalk. + * This example shows how to: + * + */ +public final class OpenTelemetryClient { + public static void main(String[] args) throws Exception { + // Configure OpenTelemetry SDK + final String serviceName = "servicetalk-example-client"; + + final OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder() + .addSpanProcessor(BatchSpanProcessor.builder(LoggingSpanExporter.create()).build()) + .build()) + .build(); + + // Set the global OpenTelemetry instance + GlobalOpenTelemetry.set(openTelemetry); + + try (BlockingHttpClient client = HttpClients.forSingleAddress("localhost", 8080) + .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() + .componentName(serviceName) + .build()) + .buildBlocking()) { + + System.out.println("Making first request..."); + HttpResponse response1 = client.request(client.get("/hello")); + System.out.println("Response 1: " + response1.toString((name, value) -> value)); + System.out.println("Body 1: " + response1.payloadBody().toString(StandardCharsets.UTF_8)); + + System.out.println("\nMaking second request..."); + HttpResponse response2 = client.request(client.get("/world")); + System.out.println("Response 2: " + response2.toString((name, value) -> value)); + System.out.println("Body 2: " + response2.payloadBody().toString(StandardCharsets.UTF_8)); + + } + } +} diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java new file mode 100644 index 0000000000..0ca7dd22fa --- /dev/null +++ b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java @@ -0,0 +1,72 @@ +/* + * Copyright © 2025 Apple Inc. and the ServiceTalk project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.servicetalk.examples.http.opentelemetry; + +import io.servicetalk.http.netty.HttpServers; +import io.servicetalk.opentelemetry.http.OpenTelemetryHttpServiceFilter; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; + +/** + * A server that demonstrates OpenTelemetry distributed tracing with ServiceTalk. + * This example shows how to: + * + */ +public final class OpenTelemetryServer { + private static final Logger LOGGER = LoggerFactory.getLogger(OpenTelemetryServer.class); + + public static void main(String[] args) throws Exception { + // Configure OpenTelemetry SDK + final OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder() + .addSpanProcessor(BatchSpanProcessor.builder(LoggingSpanExporter.create()).build()) + .build()) + .build(); + + // Set the global OpenTelemetry instance + GlobalOpenTelemetry.set(openTelemetry); + + final InetSocketAddress bindAddress = new InetSocketAddress(8080); + HttpServers.forAddress(bindAddress) + // Use non-offloading filter for proper context propagation + .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() + .build()) + .listenBlockingAndAwait((ctx, request, responseFactory) -> { + LOGGER.info("Processing request: {} {}", request.method(), request.requestTarget()); + + // Simulate some processing work + Thread.sleep(50); + + return responseFactory.ok() + .addHeader("content-type", "text/plain") + .payloadBody(ctx.executionContext().bufferAllocator() + .fromAscii("Hello from OpenTelemetry server!")); + }).awaitShutdown(); + } +} diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/package-info.java b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/package-info.java new file mode 100644 index 0000000000..9a358c7078 --- /dev/null +++ b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2025 Apple Inc. and the ServiceTalk project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * OpenTelemetry examples showing distributed tracing with ServiceTalk. + */ +@ElementsAreNonnullByDefault +package io.servicetalk.examples.http.opentelemetry; + +import io.servicetalk.annotations.ElementsAreNonnullByDefault; diff --git a/servicetalk-examples/http/opentelemetry/src/main/resources/log4j2.xml b/servicetalk-examples/http/opentelemetry/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b220fa88cb --- /dev/null +++ b/servicetalk-examples/http/opentelemetry/src/main/resources/log4j2.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/servicetalk-opentelemetry-asynccontext/docs/antora.yml b/servicetalk-opentelemetry-asynccontext/docs/antora.yml index 28cd034e5a..9106116bba 100644 --- a/servicetalk-opentelemetry-asynccontext/docs/antora.yml +++ b/servicetalk-opentelemetry-asynccontext/docs/antora.yml @@ -19,4 +19,3 @@ title: OpenTelemetry Async Context version: SNAPSHOT nav: - modules/ROOT/nav.adoc - \ No newline at end of file diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc index 341ae8cd88..30da0d81e5 100644 --- a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -1 +1 @@ -* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] \ No newline at end of file +* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc index d80ba50037..4ff26b1e14 100644 --- a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc +++ b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc @@ -29,4 +29,4 @@ For detailed usage examples, see the OpenTelemetry examples in the ServiceTalk e == Related Documentation * xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] -* xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry HTTP Tracing] \ No newline at end of file +* xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry HTTP Tracing] diff --git a/servicetalk-opentelemetry-http/docs/antora.yml b/servicetalk-opentelemetry-http/docs/antora.yml index 80be15a878..3744cd10c1 100644 --- a/servicetalk-opentelemetry-http/docs/antora.yml +++ b/servicetalk-opentelemetry-http/docs/antora.yml @@ -19,4 +19,3 @@ title: OpenTelemetry HTTP version: SNAPSHOT nav: - modules/ROOT/nav.adoc - \ No newline at end of file diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc index ad35886007..de189fcd0a 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -1,2 +1,2 @@ * xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry HTTP Tracing] -** xref:{page-version}@servicetalk-opentelemetry-http::tracing.adoc[Tracing Guide] \ No newline at end of file +** xref:{page-version}@servicetalk-opentelemetry-http::tracing.adoc[Tracing Guide] diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc index 4ffd21417c..1845c2bb38 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc @@ -19,7 +19,7 @@ The `servicetalk-opentelemetry-http` module provides HTTP tracing filters that i === Client-Side Tracing * link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpRequesterFilter.java[OpenTelemetryHttpRequesterFilter] - HTTP client tracing filter -=== Server-Side Tracing +=== Server-Side Tracing * link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpServiceFilter.java[OpenTelemetryHttpServiceFilter] - HTTP server tracing filter === Configuration @@ -63,4 +63,4 @@ This module requires: * xref:{page-version}@servicetalk-opentelemetry-http::tracing.adoc[Detailed Tracing Guide] * xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] -* xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] \ No newline at end of file +* xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc index 8e772be287..b0c4f830f3 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc @@ -130,4 +130,4 @@ For complete working examples, see the OpenTelemetry examples in the ServiceTalk * xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] * xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] -* https://opentelemetry.io/docs/instrumentation/java/[OpenTelemetry Java Documentation] \ No newline at end of file +* https://opentelemetry.io/docs/instrumentation/java/[OpenTelemetry Java Documentation] diff --git a/settings.gradle b/settings.gradle index 4e540ceb72..e1fe90a8bf 100755 --- a/settings.gradle +++ b/settings.gradle @@ -66,6 +66,7 @@ include "servicetalk-annotations", "servicetalk-examples:http:http2", "servicetalk-examples:http:jaxrs", "servicetalk-examples:http:metadata", + "servicetalk-examples:http:opentelemetry", "servicetalk-examples:http:opentracing", "servicetalk-examples:http:observer", "servicetalk-examples:http:retry", From 324fbe2cbde39e3b76c2005acde37968f2782aaf Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 15:55:15 -0600 Subject: [PATCH 03/13] Finish polishing structure --- docs/generation/site-local.yml | 3 - docs/generation/site-remote.yml | 4 +- .../ROOT/pages/_partials/nav-versioned.adoc | 1 - .../http/opentelemetry/build.gradle | 2 +- .../docs/antora.yml | 21 -- .../docs/modules/ROOT/nav.adoc | 5 - .../ROOT/pages/_partials/nav-versioned.adoc | 1 - .../docs/modules/ROOT/pages/index.adoc | 32 --- .../docs/antora.yml | 2 +- .../ROOT/pages/_partials/nav-versioned.adoc | 3 +- .../docs/modules/ROOT/pages/index.adoc | 190 ++++++++++++++++-- .../docs/modules/ROOT/pages/tracing.adoc | 133 ------------ 12 files changed, 176 insertions(+), 221 deletions(-) delete mode 100644 servicetalk-opentelemetry-asynccontext/docs/antora.yml delete mode 100644 servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc delete mode 100644 servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc delete mode 100644 servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc delete mode 100644 servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc diff --git a/docs/generation/site-local.yml b/docs/generation/site-local.yml index 428d618eae..de852e0a4f 100644 --- a/docs/generation/site-local.yml +++ b/docs/generation/site-local.yml @@ -57,9 +57,6 @@ content: - url: ../../ branches: HEAD start_path: servicetalk-traffic-resilience-http/docs - - url: ../../ - branches: HEAD - start_path: servicetalk-opentelemetry-asynccontext/docs - url: ../../ branches: HEAD start_path: servicetalk-opentelemetry-http/docs diff --git a/docs/generation/site-remote.yml b/docs/generation/site-remote.yml index ee8547936b..8864bdc326 100644 --- a/docs/generation/site-remote.yml +++ b/docs/generation/site-remote.yml @@ -72,9 +72,7 @@ content: branches: main tags: [0.42.59] start_path: servicetalk-traffic-resilience-http/docs - - url: https://github.com/apple/servicetalk.git - branches: main - start_path: servicetalk-opentelemetry-asynccontext/docs + tags: [] - url: https://github.com/apple/servicetalk.git branches: main start_path: servicetalk-opentelemetry-http/docs diff --git a/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/docs/modules/ROOT/pages/_partials/nav-versioned.adoc index 4a79ad6ce9..e07972bc42 100644 --- a/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ b/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -50,7 +50,6 @@ include::{page-version}@servicetalk-traffic-resilience-http:ROOT:partial$nav-ver * Observability + -- -include::{page-version}@servicetalk-opentelemetry-asynccontext:ROOT:partial$nav-versioned.adoc[] include::{page-version}@servicetalk-opentelemetry-http:ROOT:partial$nav-versioned.adoc[] -- + diff --git a/servicetalk-examples/http/opentelemetry/build.gradle b/servicetalk-examples/http/opentelemetry/build.gradle index bfce6456a5..3a8155b409 100644 --- a/servicetalk-examples/http/opentelemetry/build.gradle +++ b/servicetalk-examples/http/opentelemetry/build.gradle @@ -20,7 +20,7 @@ apply from: "../../gradle/idea.gradle" dependencies { implementation project(":servicetalk-annotations") implementation project(":servicetalk-http-netty") - implementation project(":servicetalk-opentelemetry-http") // OpenTelemetry HTTP client/server filters + implementation project(":servicetalk-opentelemetry-http") // OpenTelemetry client/server filters implementation project(":servicetalk-opentelemetry-asynccontext") // OpenTelemetry async context propagation // OpenTelemetry Java SDK diff --git a/servicetalk-opentelemetry-asynccontext/docs/antora.yml b/servicetalk-opentelemetry-asynccontext/docs/antora.yml deleted file mode 100644 index 9106116bba..0000000000 --- a/servicetalk-opentelemetry-asynccontext/docs/antora.yml +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright © 2025 Apple Inc. and the ServiceTalk project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: servicetalk-opentelemetry-asynccontext -title: OpenTelemetry Async Context -version: SNAPSHOT -nav: - - modules/ROOT/nav.adoc diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc deleted file mode 100644 index 8ddc690987..0000000000 --- a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/nav.adoc +++ /dev/null @@ -1,5 +0,0 @@ -ifndef::page-version[] -:page-version: SNAPSHOT -endif::[] - -include::{page-version}@servicetalk-opentelemetry-asynccontext:ROOT:partial$nav-versioned.adoc[] diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc deleted file mode 100644 index 30da0d81e5..0000000000 --- a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ /dev/null @@ -1 +0,0 @@ -* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] diff --git a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc deleted file mode 100644 index 4ff26b1e14..0000000000 --- a/servicetalk-opentelemetry-asynccontext/docs/modules/ROOT/pages/index.adoc +++ /dev/null @@ -1,32 +0,0 @@ -// Configure {source-root} values based on how this document is rendered: on GitHub or not -ifdef::env-github[] -:source-root: -endif::[] -ifndef::env-github[] -ifndef::source-root[:source-root: https://github.com/apple/servicetalk/blob/{page-origin-refname}] -endif::[] - -= OpenTelemetry Async Context - -ServiceTalk provides OpenTelemetry integration through async context propagation that ensures OpenTelemetry context and spans are properly maintained across asynchronous boundaries. - -== Overview - -The `servicetalk-opentelemetry-asynccontext` module provides the link:{source-root}/servicetalk-opentelemetry-asynccontext/src/main/java/io/servicetalk/opentelemetry/asynccontext/OtelCapturedContextProvider.java[OtelCapturedContextProvider] which integrates OpenTelemetry's context propagation with ServiceTalk's xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[Asynchronous Context] system. - -== Key Features - -* **Automatic Context Propagation**: OpenTelemetry context is automatically captured and restored across ServiceTalk's asynchronous boundaries -* **Thread Safety**: Works correctly in ServiceTalk's multi-threaded, asynchronous execution model -* **Zero Configuration**: Automatically discovered and enabled via Java's ServiceLoader mechanism - -== Usage - -This module is typically used in conjunction with the `servicetalk-opentelemetry-http` module to provide end-to-end tracing for HTTP services and clients. - -For detailed usage examples, see the OpenTelemetry examples in the ServiceTalk examples module. - -== Related Documentation - -* xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] -* xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry HTTP Tracing] diff --git a/servicetalk-opentelemetry-http/docs/antora.yml b/servicetalk-opentelemetry-http/docs/antora.yml index 3744cd10c1..31689b7352 100644 --- a/servicetalk-opentelemetry-http/docs/antora.yml +++ b/servicetalk-opentelemetry-http/docs/antora.yml @@ -15,7 +15,7 @@ # name: servicetalk-opentelemetry-http -title: OpenTelemetry HTTP +title: OpenTelemetry version: SNAPSHOT nav: - modules/ROOT/nav.adoc diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc index de189fcd0a..bbf7172a4c 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -1,2 +1 @@ -* xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry HTTP Tracing] -** xref:{page-version}@servicetalk-opentelemetry-http::tracing.adoc[Tracing Guide] +* xref:{page-version}@servicetalk-opentelemetry-http::index.adoc[OpenTelemetry Tracing] diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc index 1845c2bb38..5c22b69475 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc @@ -6,61 +6,215 @@ ifndef::env-github[] ifndef::source-root[:source-root: https://github.com/apple/servicetalk/blob/{page-origin-refname}] endif::[] -= OpenTelemetry HTTP Tracing += OpenTelemetry Tracing -ServiceTalk provides comprehensive OpenTelemetry tracing support for HTTP clients and servers through a set of filters that automatically instrument HTTP requests and responses. +ServiceTalk provides comprehensive OpenTelemetry tracing support for HTTP and gRPC clients and servers through a set of filters that automatically instrument requests and responses. == Overview -The `servicetalk-opentelemetry-http` module provides HTTP tracing filters that integrate with OpenTelemetry to automatically create spans for HTTP operations, propagate trace context, and collect telemetry data. +The `servicetalk-opentelemetry-http` module provides tracing filters that integrate with OpenTelemetry to automatically create spans for HTTP and gRPC operations, propagate trace context, and collect telemetry data. + +IMPORTANT: The `servicetalk-opentelemetry-asynccontext` module must be on your classpath for proper async context propagation. You need to explicitly add both `servicetalk-opentelemetry-http` and `servicetalk-opentelemetry-asynccontext` as dependencies to your project. == Key Components === Client-Side Tracing -* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpRequesterFilter.java[OpenTelemetryHttpRequesterFilter] - HTTP client tracing filter +* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpRequesterFilter.java[OpenTelemetryHttpRequesterFilter] - Client tracing filter for HTTP and gRPC === Server-Side Tracing -* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpServiceFilter.java[OpenTelemetryHttpServiceFilter] - HTTP server tracing filter +* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpServiceFilter.java[OpenTelemetryHttpServiceFilter] - Server tracing filter for HTTP and gRPC === Configuration * link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryFilterBuilder.java[OpenTelemetryFilterBuilder] - Base builder for configuring tracing options == Key Features -* **Automatic Span Creation**: Creates spans for HTTP requests and responses following OpenTelemetry semantic conventions -* **Context Propagation**: Propagates trace context across HTTP boundaries using standard headers +* **Automatic Span Creation**: Creates spans for HTTP and gRPC requests and responses following OpenTelemetry semantic conventions +* **Context Propagation**: Propagates trace context across service boundaries using standard headers * **Configurable Attributes**: Capture custom request and response headers as span attributes -* **Metrics Support**: Optional collection of HTTP operation metrics * **gRPC Support**: Specialized support for gRPC over HTTP/2 tracing * **Filter Ordering**: Proper integration with ServiceTalk's filter chain for correct context handling == Quick Start +NOTE: For complete dependency setup and OpenTelemetry SDK configuration, see the working examples in the ServiceTalk examples module. + === Client Configuration -```java + +[source,java] +---- HttpClient client = HttpClients.forSingleAddress("example.com", 80) .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() .componentName("my-client") + .capturedRequestHeaders(Arrays.asList("user-agent", "x-request-id")) + .capturedResponseHeaders(Arrays.asList("content-type", "x-response-id")) .build()) .build(); -``` +---- === Server Configuration -```java + +[source,java] +---- +HttpServerBuilder.forAddress(localAddress(0)) + // IMPORTANT: Use non-offloading filter for proper context propagation + .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() + .capturedRequestHeaders(Arrays.asList("user-agent", "x-request-id")) + .capturedResponseHeaders(Arrays.asList("content-type")) + .build()) + + // Add request draining AFTER OpenTelemetry filter + .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) + + .listen(service); +---- + +== Advanced Configuration + +=== Capturing Custom Headers + +[source,java] +---- +// Capture specific request and response headers as span attributes +OpenTelemetryHttpRequesterFilter filter = new OpenTelemetryHttpRequesterFilter.Builder() + .componentName("my-client") + .capturedRequestHeaders(Arrays.asList("x-custom-header", "authorization")) + .capturedResponseHeaders(Arrays.asList("x-response-id", "content-type")) + .build(); +---- + +== Filter Ordering Guidelines + +Proper filter ordering is crucial for OpenTelemetry tracing to work correctly with other ServiceTalk features. + +=== Server Filters + +For servers, use non-offloading filters for proper context propagation and pay special attention to filter ordering: + +[source,java] +---- HttpServerBuilder.forAddress(localAddress(0)) + // OpenTelemetry filter MUST be first for proper context propagation .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() .build()) + + // Request draining filter MUST come after OpenTelemetry filter + // This ensures tracing information is captured for auto-drained requests + .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) + + // Exception mapping should come after tracing to ensure correct status codes + .appendServiceFilter(HttpExceptionMapperServiceFilter.create()) + + // Other filters can follow .listen(service); -``` +---- + +**Important:** The `HttpRequestAutoDrainingServiceFilter` must be placed *after* the OpenTelemetry filter. This is critical for server-side tracing accuracy, particularly for requests like GET where the request body is typically ignored. If auto-draining occurs before the OpenTelemetry filter processes the request, tracing information may be incomplete or incorrect. + +=== Client Filters + +When using multiple filters on clients, place the OpenTelemetry filter appropriately in the chain: + +[source,java] +---- +HttpClient client = HttpClients.forSingleAddress("example.com", 80) + // Logging or other observability filters before tracing + .appendClientFilter(loggingFilter) + + // OpenTelemetry filter for span creation and context propagation + .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() + .componentName("my-client") + .build()) + + // Retry and other resilience filters after tracing + .appendClientFilter(retryFilter) + .build(); +---- + +=== Filter Ordering Best Practices + +1. **OpenTelemetry filters should be among the first filters** to ensure proper context establishment +2. **Use non-offloading filters** (`appendNonOffloadingServiceFilter`) for OpenTelemetry filters to maintain context +3. **Request draining must come after OpenTelemetry** on the server side +4. **Exception mapping should come after OpenTelemetry** to ensure trace status reflects actual response codes +5. **Lifecycle observers should come after OpenTelemetry** to see correct span information + +== Context Propagation + +OpenTelemetry context is automatically propagated through multiple mechanisms to ensure traces are correlated correctly across service boundaries and async operations. + +=== Header Propagation + +OpenTelemetry context is automatically injected into and extracted from headers using the standard OpenTelemetry propagation format: + +* **W3C Trace Context** (`traceparent`, `tracestate` headers) +* **B3 Propagation** (if configured) +* **Custom propagators** (if configured in the OpenTelemetry SDK) + +[source,java] +---- +// Context is automatically propagated via headers +HttpResponse response = client.request(client.get("/api/endpoint")); +// The server will receive trace context via HTTP headers +---- + +=== Async Context Integration + +ServiceTalk's async context system ensures OpenTelemetry context is maintained across: + +* **Thread boundaries** during async operations +* **Publisher/Subscriber chains** in reactive streams +* **Executor transitions** when work is offloaded +* **Filter chains** where context must be preserved + +This integration is provided by the `servicetalk-opentelemetry-asynccontext` module, which implements the `CapturedContextProvider` interface. + +=== Context Scope Management + +OpenTelemetry spans are automatically activated and deactivated at appropriate points: + +[source,java] +---- +// Client side: span is active during request processing +client.request(client.get("/api")) + .beforeOnSuccess(response -> { + // Current span is still active here + Span currentSpan = Span.current(); + currentSpan.setStatus(StatusCode.OK); + }); + +// Server side: span is active during service method execution +service.handle((ctx, request, responseFactory) -> { + // Current span is active and contains trace context from client + Span currentSpan = Span.current(); + currentSpan.addEvent("Processing request"); + return responseFactory.ok(); +}); +---- + +== gRPC Support + +The tracing filters provide specialized support for gRPC over HTTP/2: + +* Automatic detection of gRPC requests +* gRPC-specific span naming and attributes +* Proper status code mapping + +== Troubleshooting + +=== Common Issues + +**Context Not Propagating** +Ensure `servicetalk-opentelemetry-asynccontext` is on the classpath and the filter is properly ordered. + +**Missing Spans** +Verify OpenTelemetry is properly configured and the global OpenTelemetry instance is set. -== Dependencies +== Examples -This module requires: -* `servicetalk-opentelemetry-asynccontext` for async context propagation -* OpenTelemetry Java libraries for span creation and context management +For complete working examples, see the OpenTelemetry examples in the ServiceTalk examples module. == Related Documentation -* xref:{page-version}@servicetalk-opentelemetry-http::tracing.adoc[Detailed Tracing Guide] -* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] * xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] +* https://opentelemetry.io/docs/instrumentation/java/[OpenTelemetry Java Documentation] diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc deleted file mode 100644 index b0c4f830f3..0000000000 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/tracing.adoc +++ /dev/null @@ -1,133 +0,0 @@ -// Configure {source-root} values based on how this document is rendered: on GitHub or not -ifdef::env-github[] -:source-root: -endif::[] -ifndef::env-github[] -ifndef::source-root[:source-root: https://github.com/apple/servicetalk/blob/{page-origin-refname}] -endif::[] - -= OpenTelemetry HTTP Tracing Guide - -This guide provides detailed information on using OpenTelemetry HTTP tracing filters with ServiceTalk. - -== Quick Start - -=== Prerequisites - -Before using OpenTelemetry tracing, ensure you have: - -* OpenTelemetry Java libraries in your classpath -* `servicetalk-opentelemetry-asynccontext` module for context propagation -* `servicetalk-opentelemetry-http` module for HTTP tracing filters - -=== Basic Client Setup - -[source,java] ----- -// Example client configuration -HttpClient client = HttpClients.forSingleAddress("example.com", 80) - .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() - .componentName("my-client") - .build()) - .build(); ----- - -=== Basic Server Setup - -[source,java] ----- -// Example server configuration -HttpServerBuilder.forAddress(localAddress(0)) - .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() - .build()) - .listen(service); ----- - -== Advanced Configuration - -=== Capturing Custom Headers - -[source,java] ----- -// Capture specific request and response headers as span attributes -OpenTelemetryHttpRequesterFilter filter = new OpenTelemetryHttpRequesterFilter.Builder() - .componentName("my-client") - .capturedRequestHeaders(Arrays.asList("x-custom-header", "authorization")) - .capturedResponseHeaders(Arrays.asList("x-response-id", "content-type")) - .build(); ----- - -=== Enabling Metrics - -[source,java] ----- -// Enable OpenTelemetry metrics collection -OpenTelemetryHttpServiceFilter filter = new OpenTelemetryHttpServiceFilter.Builder() - .enableMetrics(true) - .build(); ----- - -== Filter Ordering Guidelines - -=== Client Filters -When using multiple filters on clients, place the OpenTelemetry filter appropriately in the chain: - -[source,java] ----- -HttpClient client = HttpClients.forSingleAddress("example.com", 80) - .appendClientFilter(loggingFilter) // Before tracing for proper context - .appendClientFilter(openTelemetryFilter) // Tracing filter - .appendClientFilter(retryFilter) // After tracing - .build(); ----- - -=== Server Filters -For servers, use non-offloading filters for proper context propagation: - -[source,java] ----- -HttpServerBuilder.forAddress(localAddress(0)) - .appendNonOffloadingServiceFilter(openTelemetryFilter) // First for context - .appendNonOffloadingServiceFilter(autoDrainingFilter) // After tracing - .appendServiceFilter(exceptionMapperFilter) // After tracing - .listen(service); ----- - -== Context Propagation - -OpenTelemetry context is automatically propagated through: - -* HTTP headers using standard OpenTelemetry propagation format -* ServiceTalk's async context system via `servicetalk-opentelemetry-asynccontext` -* Thread boundaries during async operations - -== gRPC Support - -The HTTP tracing filters provide specialized support for gRPC over HTTP/2: - -* Automatic detection of gRPC requests -* gRPC-specific span naming and attributes -* Proper status code mapping - -== Troubleshooting - -=== Common Issues - -**Context Not Propagating** -Ensure `servicetalk-opentelemetry-asynccontext` is on the classpath and the filter is properly ordered. - -**Missing Spans** -Verify OpenTelemetry is properly configured and the global OpenTelemetry instance is set. - -**Performance Impact** -Consider disabling metrics if not needed, as they add overhead to request processing. - -== Examples - -For complete working examples, see the OpenTelemetry examples in the ServiceTalk examples module. - -== Related Documentation - -* xref:{page-version}@servicetalk-opentelemetry-asynccontext::index.adoc[OpenTelemetry Async Context] -* xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[ServiceTalk Asynchronous Context] -* https://opentelemetry.io/docs/instrumentation/java/[OpenTelemetry Java Documentation] From 276be5b6ba9dc712186ee590d71b6194583f8f98 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 16:57:31 -0600 Subject: [PATCH 04/13] Add some other filters in the examples --- .../http/opentelemetry/build.gradle | 1 + .../opentelemetry/OpenTelemetryClient.java | 3 +++ .../opentelemetry/OpenTelemetryServer.java | 20 ++++++++++++++----- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/servicetalk-examples/http/opentelemetry/build.gradle b/servicetalk-examples/http/opentelemetry/build.gradle index 3a8155b409..8474ea0d2c 100644 --- a/servicetalk-examples/http/opentelemetry/build.gradle +++ b/servicetalk-examples/http/opentelemetry/build.gradle @@ -20,6 +20,7 @@ apply from: "../../gradle/idea.gradle" dependencies { implementation project(":servicetalk-annotations") implementation project(":servicetalk-http-netty") + implementation project(":servicetalk-http-utils") // For HttpRequestAutoDrainingServiceFilter implementation project(":servicetalk-opentelemetry-http") // OpenTelemetry client/server filters implementation project(":servicetalk-opentelemetry-asynccontext") // OpenTelemetry async context propagation diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java index 1d5a24ec60..361dfa5a55 100644 --- a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java +++ b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java @@ -34,6 +34,7 @@ *
    *
  • Configure OpenTelemetry SDK with logging exporter
  • *
  • Set up ServiceTalk HTTP client with OpenTelemetry tracing filter
  • + *
  • Demonstrate proper client-side filter ordering
  • *
  • Automatically capture HTTP request/response spans
  • *
  • Propagate trace context to downstream services
  • *
@@ -53,6 +54,8 @@ public static void main(String[] args) throws Exception { GlobalOpenTelemetry.set(openTelemetry); try (BlockingHttpClient client = HttpClients.forSingleAddress("localhost", 8080) + // IMPORTANT: OpenTelemetry filter should be placed EARLY in the filter chain + // This ensures proper span creation and context propagation for downstream filters .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() .componentName(serviceName) .build()) diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java index 0ca7dd22fa..dc9c036cf5 100644 --- a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java +++ b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java @@ -16,6 +16,7 @@ package io.servicetalk.examples.http.opentelemetry; import io.servicetalk.http.netty.HttpServers; +import io.servicetalk.http.utils.HttpRequestAutoDrainingServiceFilter; import io.servicetalk.opentelemetry.http.OpenTelemetryHttpServiceFilter; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -27,14 +28,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.InetSocketAddress; - /** * A server that demonstrates OpenTelemetry distributed tracing with ServiceTalk. * This example shows how to: *
    *
  • Configure OpenTelemetry SDK with logging exporter
  • *
  • Set up ServiceTalk HTTP server with OpenTelemetry tracing filter
  • + *
  • Demonstrate proper filter ordering for accurate tracing
  • *
  • Automatically capture HTTP request/response spans
  • *
*/ @@ -52,11 +52,21 @@ public static void main(String[] args) throws Exception { // Set the global OpenTelemetry instance GlobalOpenTelemetry.set(openTelemetry); - final InetSocketAddress bindAddress = new InetSocketAddress(8080); - HttpServers.forAddress(bindAddress) - // Use non-offloading filter for proper context propagation + HttpServers.forPort(8080) + // CRITICAL: OpenTelemetry filter MUST be first for proper context propagation + // Use non-offloading filter to maintain async context across threads .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() .build()) + + // IMPORTANT: Request draining filter MUST come after OpenTelemetry filter + // This ensures tracing information is captured for auto-drained requests (e.g., GET requests) + // If auto-draining occurs before OpenTelemetry filter processes the request, + // tracing information may be incomplete or incorrect + .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) + + // Other filters can be added after the critical ordering above + // Exception mapping, logging, etc. should come after OpenTelemetry and request draining + .listenBlockingAndAwait((ctx, request, responseFactory) -> { LOGGER.info("Processing request: {} {}", request.method(), request.requestTarget()); From 0822a7664a409d2fb9665dbb8fa529883602f5a6 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 17:47:09 -0600 Subject: [PATCH 05/13] One omission --- settings.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle b/settings.gradle index e1fe90a8bf..a6b781b3f4 100755 --- a/settings.gradle +++ b/settings.gradle @@ -153,6 +153,7 @@ project(":servicetalk-examples:http:timeout").name = "servicetalk-examples-http- project(":servicetalk-examples:http:jaxrs").name = "servicetalk-examples-http-jaxrs" project(":servicetalk-examples:http:loadbalancer").name = "servicetalk-examples-http-loadbalancer" project(":servicetalk-examples:http:metadata").name = "servicetalk-examples-http-metadata" +project(":servicetalk-examples:http:opentelemetry").name = "servicetalk-examples-http-opentelemetry" project(":servicetalk-examples:http:opentracing").name = "servicetalk-examples-http-opentracing" project(":servicetalk-examples:http:observer").name = "servicetalk-examples-http-observer" project(":servicetalk-examples:http:retry").name = "servicetalk-examples-http-retry" From 3777ab87124ecc5af43f057b1a30cf986c3827d4 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 17:54:00 -0600 Subject: [PATCH 06/13] Why is lockfile not happy? --- scripts/check-lockfiles.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/check-lockfiles.sh b/scripts/check-lockfiles.sh index e4eae59515..20170c8e3e 100755 --- a/scripts/check-lockfiles.sh +++ b/scripts/check-lockfiles.sh @@ -25,6 +25,7 @@ if [ -n "$status" ]; then echo "You MUST regenerate lockfiles using: ./gradlew resolveAndLockAll --write-locks" echo "The following files are not updated:" echo "$status" + git diff exit 1 else echo "All Gradle lockfiles are up to date" From e21ef88153aa5edd2e23a87e220c61f60d3c9b9e Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 18:02:03 -0600 Subject: [PATCH 07/13] Fix lockfile --- .../http/opentelemetry/gradle.lockfile | 40 +++++++++---------- .../opentelemetry/OpenTelemetryServer.java | 5 +-- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/servicetalk-examples/http/opentelemetry/gradle.lockfile b/servicetalk-examples/http/opentelemetry/gradle.lockfile index e2da515d09..60c9218ccf 100644 --- a/servicetalk-examples/http/opentelemetry/gradle.lockfile +++ b/servicetalk-examples/http/opentelemetry/gradle.lockfile @@ -5,26 +5,26 @@ com.google.code.findbugs:jsr305:3.0.2=compileClasspath,runtimeClasspath com.squareup.okhttp3:okhttp:4.12.0=runtimeClasspath com.squareup.okio:okio-jvm:3.6.0=runtimeClasspath com.squareup.okio:okio:3.6.0=runtimeClasspath -io.netty:netty-bom:4.1.124.Final=runtimeClasspath -io.netty:netty-buffer:4.1.124.Final=runtimeClasspath -io.netty:netty-codec-dns:4.1.124.Final=runtimeClasspath -io.netty:netty-codec-http2:4.1.124.Final=runtimeClasspath -io.netty:netty-codec-http:4.1.124.Final=runtimeClasspath -io.netty:netty-codec:4.1.124.Final=runtimeClasspath -io.netty:netty-common:4.1.124.Final=runtimeClasspath -io.netty:netty-handler:4.1.124.Final=runtimeClasspath -io.netty:netty-resolver-dns-classes-macos:4.1.124.Final=runtimeClasspath -io.netty:netty-resolver-dns-native-macos:4.1.124.Final=runtimeClasspath -io.netty:netty-resolver-dns:4.1.124.Final=runtimeClasspath -io.netty:netty-resolver:4.1.124.Final=runtimeClasspath -io.netty:netty-tcnative-boringssl-static:2.0.72.Final=runtimeClasspath -io.netty:netty-tcnative-classes:2.0.72.Final=runtimeClasspath -io.netty:netty-transport-classes-epoll:4.1.124.Final=runtimeClasspath -io.netty:netty-transport-classes-kqueue:4.1.124.Final=runtimeClasspath -io.netty:netty-transport-native-epoll:4.1.124.Final=runtimeClasspath -io.netty:netty-transport-native-kqueue:4.1.124.Final=runtimeClasspath -io.netty:netty-transport-native-unix-common:4.1.124.Final=runtimeClasspath -io.netty:netty-transport:4.1.124.Final=runtimeClasspath +io.netty:netty-bom:4.1.127.Final=runtimeClasspath +io.netty:netty-buffer:4.1.127.Final=runtimeClasspath +io.netty:netty-codec-dns:4.1.127.Final=runtimeClasspath +io.netty:netty-codec-http2:4.1.127.Final=runtimeClasspath +io.netty:netty-codec-http:4.1.127.Final=runtimeClasspath +io.netty:netty-codec:4.1.127.Final=runtimeClasspath +io.netty:netty-common:4.1.127.Final=runtimeClasspath +io.netty:netty-handler:4.1.127.Final=runtimeClasspath +io.netty:netty-resolver-dns-classes-macos:4.1.127.Final=runtimeClasspath +io.netty:netty-resolver-dns-native-macos:4.1.127.Final=runtimeClasspath +io.netty:netty-resolver-dns:4.1.127.Final=runtimeClasspath +io.netty:netty-resolver:4.1.127.Final=runtimeClasspath +io.netty:netty-tcnative-boringssl-static:2.0.73.Final=runtimeClasspath +io.netty:netty-tcnative-classes:2.0.73.Final=runtimeClasspath +io.netty:netty-transport-classes-epoll:4.1.127.Final=runtimeClasspath +io.netty:netty-transport-classes-kqueue:4.1.127.Final=runtimeClasspath +io.netty:netty-transport-native-epoll:4.1.127.Final=runtimeClasspath +io.netty:netty-transport-native-kqueue:4.1.127.Final=runtimeClasspath +io.netty:netty-transport-native-unix-common:4.1.127.Final=runtimeClasspath +io.netty:netty-transport:4.1.127.Final=runtimeClasspath io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.14.0=runtimeClasspath io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.14.0=runtimeClasspath io.opentelemetry.semconv:opentelemetry-semconv:1.30.0=runtimeClasspath diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java index dc9c036cf5..25beba558b 100644 --- a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java +++ b/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java @@ -57,16 +57,15 @@ public static void main(String[] args) throws Exception { // Use non-offloading filter to maintain async context across threads .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() .build()) - + // IMPORTANT: Request draining filter MUST come after OpenTelemetry filter // This ensures tracing information is captured for auto-drained requests (e.g., GET requests) // If auto-draining occurs before OpenTelemetry filter processes the request, // tracing information may be incomplete or incorrect .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) - + // Other filters can be added after the critical ordering above // Exception mapping, logging, etc. should come after OpenTelemetry and request draining - .listenBlockingAndAwait((ctx, request, responseFactory) -> { LOGGER.info("Processing request: {} {}", request.method(), request.requestTarget()); From dcbe51d79bf8e84d0d6e5b988331ebee796c079d Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Tue, 23 Sep 2025 19:37:46 -0600 Subject: [PATCH 08/13] Revert debug changes to check-lockfile --- scripts/check-lockfiles.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/check-lockfiles.sh b/scripts/check-lockfiles.sh index 20170c8e3e..e4eae59515 100755 --- a/scripts/check-lockfiles.sh +++ b/scripts/check-lockfiles.sh @@ -25,7 +25,6 @@ if [ -n "$status" ]; then echo "You MUST regenerate lockfiles using: ./gradlew resolveAndLockAll --write-locks" echo "The following files are not updated:" echo "$status" - git diff exit 1 else echo "All Gradle lockfiles are up to date" From 7c3abecaccdfaf12f1a1a9533f0ede462d2bcca4 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Thu, 2 Oct 2025 09:11:42 -0600 Subject: [PATCH 09/13] Apply Idel suggestions from code review Co-authored-by: Idel Pivnitskiy --- docs/generation/site-remote.yml | 2 +- servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc | 2 +- .../docs/modules/ROOT/pages/index.adoc | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/generation/site-remote.yml b/docs/generation/site-remote.yml index 123ca374f1..c03c969af6 100644 --- a/docs/generation/site-remote.yml +++ b/docs/generation/site-remote.yml @@ -72,9 +72,9 @@ content: branches: main tags: [0.42.60] start_path: servicetalk-traffic-resilience-http/docs - tags: [] - url: https://github.com/apple/servicetalk.git branches: main + tags: [] start_path: servicetalk-opentelemetry-http/docs asciidoc: attributes: diff --git a/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc b/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc index a97e827378..aae6ee28bc 100644 --- a/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc +++ b/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc @@ -271,7 +271,7 @@ This example demonstrates the following: - automatically generate and propagate distributed tracing metadata using OpenTelemetry - configure OpenTelemetry SDK with logging span exporter for local development -- integrate ServiceTalk HTTP filters with OpenTelemetry tracing +- use of ServiceTalk HTTP filters for OpenTelemetry tracing - proper async context propagation for span correlation Using the following classes: diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc index 5c22b69475..bb17fca094 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc @@ -103,7 +103,7 @@ HttpServerBuilder.forAddress(localAddress(0)) .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) // Exception mapping should come after tracing to ensure correct status codes - .appendServiceFilter(HttpExceptionMapperServiceFilter.create()) + .appendNonOffloadingServiceFilter(HttpExceptionMapperServiceFilter.INSTANCE) // Other filters can follow .listen(service); @@ -134,7 +134,7 @@ HttpClient client = HttpClients.forSingleAddress("example.com", 80) === Filter Ordering Best Practices 1. **OpenTelemetry filters should be among the first filters** to ensure proper context establishment -2. **Use non-offloading filters** (`appendNonOffloadingServiceFilter`) for OpenTelemetry filters to maintain context +2. **Use non-offloading filters** (`appendNonOffloadingServiceFilter`) for OpenTelemetry filters on the server-side to ensure earlier context establishment 3. **Request draining must come after OpenTelemetry** on the server side 4. **Exception mapping should come after OpenTelemetry** to ensure trace status reflects actual response codes 5. **Lifecycle observers should come after OpenTelemetry** to see correct span information From d6838535400c16e106167d45bda804d5bf42b807 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Thu, 2 Oct 2025 12:34:13 -0600 Subject: [PATCH 10/13] More Idel feedback --- .../ROOT/pages/_partials/nav-versioned.adoc | 2 +- .../docs/modules/ROOT/pages/http/index.adoc | 10 ++-- .../build.gradle | 13 +++-- .../gradle.lockfile | 0 .../tracing/OpenTelemetryTracingClient.java} | 16 +++++-- .../tracing/OpenTelemetryTracingServer.java} | 15 ++++-- .../opentelemetry/tracing}/package-info.java | 2 +- .../src/main/resources/log4j2.xml | 0 .../docs/modules/ROOT/pages/index.adoc | 47 ++++++++++--------- settings.gradle | 4 +- 10 files changed, 64 insertions(+), 45 deletions(-) rename servicetalk-examples/http/{opentelemetry => opentelemetry-tracing}/build.gradle (76%) rename servicetalk-examples/http/{opentelemetry => opentelemetry-tracing}/gradle.lockfile (100%) rename servicetalk-examples/http/{opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java => opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java} (83%) rename servicetalk-examples/http/{opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java => opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingServer.java} (86%) rename servicetalk-examples/http/{opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry => opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing}/package-info.java (93%) rename servicetalk-examples/http/{opentelemetry => opentelemetry-tracing}/src/main/resources/log4j2.xml (100%) diff --git a/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc index 274e04253b..77c164df13 100644 --- a/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc +++ b/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc @@ -14,7 +14,7 @@ ** xref:{page-version}@servicetalk-examples::http/index.adoc#Observer[Observer] ** xref:{page-version}@servicetalk-examples::http/index.adoc#LoadBalancer[LoadBalancer] ** xref:{page-version}@servicetalk-examples::http/index.adoc#OpenTracing[OpenTracing] -** xref:{page-version}@servicetalk-examples::http/index.adoc#OpenTelemetry[OpenTelemetry] +** xref:{page-version}@servicetalk-examples::http/index.adoc#OpenTelemetryTracing[OpenTelemetry Tracing] ** xref:{page-version}@servicetalk-examples::http/index.adoc#Redirects[Redirects] ** xref:{page-version}@servicetalk-examples::http/index.adoc#Retries[Retries] ** xref:{page-version}@servicetalk-examples::http/index.adoc#uds[Unix Domain Sockets] diff --git a/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc b/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc index aae6ee28bc..ab0c9da9df 100644 --- a/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc +++ b/servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc @@ -264,20 +264,20 @@ Using the following classes: - link:{source-root}/servicetalk-examples/http/opentracing/src/main/java/io/servicetalk/examples/http/opentracing/ZipkinServerSimulator.java[ZipkinServerSimulator] - A server that simulates/mocks a Zipkin server, and logs requests to the console. - link:{source-root}/servicetalk-examples/http/opentracing/src/main/java/io/servicetalk/examples/http/opentracing/BraveTracingServer.java[BraveTracingServer] - A server that uses link:https://github.com/openzipkin-contrib/brave-opentracing[Brave OpenTracing] implementation. -[#OpenTelemetry] -== OpenTelemetry +[#OpenTelemetryTracing] +== OpenTelemetry Tracing This example demonstrates the following: - automatically generate and propagate distributed tracing metadata using OpenTelemetry - configure OpenTelemetry SDK with logging span exporter for local development - use of ServiceTalk HTTP filters for OpenTelemetry tracing -- proper async context propagation for span correlation +- proper xref:{page-version}@servicetalk-concurrent-api::async-context.adoc[async context propagation] for span correlation Using the following classes: -- link:{source-root}/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java[OpenTelemetryServer] - A server that demonstrates OpenTelemetry tracing configuration and automatic span generation for HTTP requests. -- link:{source-root}/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java[OpenTelemetryClient] - A client that demonstrates OpenTelemetry tracing configuration and automatic span generation for HTTP requests with context propagation. +- link:{source-root}/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingServer.java[OpenTelemetryTracingServer] - A server that demonstrates OpenTelemetry tracing configuration and automatic span generation for HTTP requests. +- link:{source-root}/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java[OpenTelemetryTracingClient] - A client that demonstrates OpenTelemetry tracing configuration and automatic span generation for HTTP requests with context propagation. [#Redirects] == Redirects diff --git a/servicetalk-examples/http/opentelemetry/build.gradle b/servicetalk-examples/http/opentelemetry-tracing/build.gradle similarity index 76% rename from servicetalk-examples/http/opentelemetry/build.gradle rename to servicetalk-examples/http/opentelemetry-tracing/build.gradle index 8474ea0d2c..29c9ad0843 100644 --- a/servicetalk-examples/http/opentelemetry/build.gradle +++ b/servicetalk-examples/http/opentelemetry-tracing/build.gradle @@ -18,17 +18,20 @@ apply plugin: "java" apply from: "../../gradle/idea.gradle" dependencies { + implementation platform("io.opentelemetry:opentelemetry-bom:$opentelemetryVersion") + implementation project(":servicetalk-annotations") implementation project(":servicetalk-http-netty") implementation project(":servicetalk-http-utils") // For HttpRequestAutoDrainingServiceFilter implementation project(":servicetalk-opentelemetry-http") // OpenTelemetry client/server filters - implementation project(":servicetalk-opentelemetry-asynccontext") // OpenTelemetry async context propagation + + runtimeOnly project(":servicetalk-opentelemetry-asynccontext") // OpenTelemetry async context propagation // OpenTelemetry Java SDK - implementation "io.opentelemetry:opentelemetry-api:$opentelemetryVersion" - implementation "io.opentelemetry:opentelemetry-sdk:$opentelemetryVersion" - implementation "io.opentelemetry:opentelemetry-exporter-logging:$opentelemetryVersion" - implementation "io.opentelemetry:opentelemetry-exporter-otlp:$opentelemetryVersion" + implementation "io.opentelemetry:opentelemetry-api" + implementation "io.opentelemetry:opentelemetry-sdk" + implementation "io.opentelemetry:opentelemetry-exporter-logging" + implementation "io.opentelemetry:opentelemetry-exporter-otlp" implementation "org.slf4j:slf4j-api:$slf4jVersion" runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" diff --git a/servicetalk-examples/http/opentelemetry/gradle.lockfile b/servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile similarity index 100% rename from servicetalk-examples/http/opentelemetry/gradle.lockfile rename to servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java similarity index 83% rename from servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java rename to servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java index 361dfa5a55..7995f6daa0 100644 --- a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryClient.java +++ b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.servicetalk.examples.http.opentelemetry; +package io.servicetalk.examples.http.opentelemetry.tracing; import io.servicetalk.http.api.BlockingHttpClient; import io.servicetalk.http.api.HttpResponse; @@ -39,7 +39,7 @@ *
  • Propagate trace context to downstream services
  • * */ -public final class OpenTelemetryClient { +public final class OpenTelemetryTracingClient { public static void main(String[] args) throws Exception { // Configure OpenTelemetry SDK final String serviceName = "servicetalk-example-client"; @@ -53,12 +53,18 @@ public static void main(String[] args) throws Exception { // Set the global OpenTelemetry instance GlobalOpenTelemetry.set(openTelemetry); + OpenTelemetryHttpRequesterFilter filter = new OpenTelemetryHttpRequesterFilter.Builder() + .componentName(serviceName) + .build(); + try (BlockingHttpClient client = HttpClients.forSingleAddress("localhost", 8080) // IMPORTANT: OpenTelemetry filter should be placed EARLY in the filter chain // This ensures proper span creation and context propagation for downstream filters - .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() - .componentName(serviceName) - .build()) + .appendClientFilter(filter) + // Adding the filter a second time as a connection filter will enable 'physical spans' + // where there will be a higher level 'logical' span and a sub-physical span for each + // request sent over the wire. + .appendConnectionFilter(filter) .buildBlocking()) { System.out.println("Making first request..."); diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingServer.java similarity index 86% rename from servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java rename to servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingServer.java index 25beba558b..9256390787 100644 --- a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/OpenTelemetryServer.java +++ b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingServer.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.servicetalk.examples.http.opentelemetry; +package io.servicetalk.examples.http.opentelemetry.tracing; +import io.servicetalk.http.api.HttpExceptionMapperServiceFilter; import io.servicetalk.http.netty.HttpServers; import io.servicetalk.http.utils.HttpRequestAutoDrainingServiceFilter; import io.servicetalk.opentelemetry.http.OpenTelemetryHttpServiceFilter; @@ -38,8 +39,8 @@ *
  • Automatically capture HTTP request/response spans
  • * */ -public final class OpenTelemetryServer { - private static final Logger LOGGER = LoggerFactory.getLogger(OpenTelemetryServer.class); +public final class OpenTelemetryTracingServer { + private static final Logger LOGGER = LoggerFactory.getLogger(OpenTelemetryTracingServer.class); public static void main(String[] args) throws Exception { // Configure OpenTelemetry SDK @@ -58,11 +59,15 @@ public static void main(String[] args) throws Exception { .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() .build()) - // IMPORTANT: Request draining filter MUST come after OpenTelemetry filter + // IMPORTANT: Request draining MUST come after OpenTelemetry filter // This ensures tracing information is captured for auto-drained requests (e.g., GET requests) // If auto-draining occurs before OpenTelemetry filter processes the request, - // tracing information may be incomplete or incorrect + // tracing information may be incomplete or incorrect. .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) + // Similarly, exception mapping should also come after the OTEL filter so that the OTEL span properly + // reflects what was sent back to the client. + .appendNonOffloadingServiceFilter(HttpExceptionMapperServiceFilter.INSTANCE) + // Other filters can be added after the critical ordering above // Exception mapping, logging, etc. should come after OpenTelemetry and request draining diff --git a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/package-info.java b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/package-info.java similarity index 93% rename from servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/package-info.java rename to servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/package-info.java index 9a358c7078..bba535168c 100644 --- a/servicetalk-examples/http/opentelemetry/src/main/java/io/servicetalk/examples/http/opentelemetry/package-info.java +++ b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/package-info.java @@ -17,6 +17,6 @@ * OpenTelemetry examples showing distributed tracing with ServiceTalk. */ @ElementsAreNonnullByDefault -package io.servicetalk.examples.http.opentelemetry; +package io.servicetalk.examples.http.opentelemetry.tracing; import io.servicetalk.annotations.ElementsAreNonnullByDefault; diff --git a/servicetalk-examples/http/opentelemetry/src/main/resources/log4j2.xml b/servicetalk-examples/http/opentelemetry-tracing/src/main/resources/log4j2.xml similarity index 100% rename from servicetalk-examples/http/opentelemetry/src/main/resources/log4j2.xml rename to servicetalk-examples/http/opentelemetry-tracing/src/main/resources/log4j2.xml diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc index bb17fca094..f1ce86c5c3 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc @@ -18,6 +18,9 @@ IMPORTANT: The `servicetalk-opentelemetry-asynccontext` module must be on your c == Key Components +=== Context Propagation +* link:{source-root}/servicetalk-opentelemetry-asynccontext/src/main/java/io/servicetalk/opentelemetry/asynccontext/OtelCapturedContextProvider.java[OtelCapturedContextProvider] - Service loaded provider that lets ServiceTalk properly propagate the OTEL context and span information. The package just needs to be on the runtime classpath. + === Client-Side Tracing * link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpRequesterFilter.java[OpenTelemetryHttpRequesterFilter] - Client tracing filter for HTTP and gRPC @@ -25,7 +28,8 @@ IMPORTANT: The `servicetalk-opentelemetry-asynccontext` module must be on your c * link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpServiceFilter.java[OpenTelemetryHttpServiceFilter] - Server tracing filter for HTTP and gRPC === Configuration -* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryFilterBuilder.java[OpenTelemetryFilterBuilder] - Base builder for configuring tracing options +* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpRequesterFilter.java[OpenTelemetryHttpRequesterFilter.Builder] - Builder for configuring tracing options for requesters. +* link:{source-root}/servicetalk-opentelemetry-http/src/main/java/io/servicetalk/opentelemetry/http/OpenTelemetryHttpServiceFilter.java[OpenTelemetryHttpServiceFilter.Builder] - Builder for configuring tracing options for services. == Key Features @@ -37,7 +41,7 @@ IMPORTANT: The `servicetalk-opentelemetry-asynccontext` module must be on your c == Quick Start -NOTE: For complete dependency setup and OpenTelemetry SDK configuration, see the working examples in the ServiceTalk examples module. +NOTE: For complete dependency setup and OpenTelemetry SDK configuration, see the working examples in the ServiceTalk examples module link:{source-root}/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing[here]. === Client Configuration @@ -46,8 +50,6 @@ NOTE: For complete dependency setup and OpenTelemetry SDK configuration, see the HttpClient client = HttpClients.forSingleAddress("example.com", 80) .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() .componentName("my-client") - .capturedRequestHeaders(Arrays.asList("user-agent", "x-request-id")) - .capturedResponseHeaders(Arrays.asList("content-type", "x-response-id")) .build()) .build(); ---- @@ -59,12 +61,11 @@ HttpClient client = HttpClients.forSingleAddress("example.com", 80) HttpServerBuilder.forAddress(localAddress(0)) // IMPORTANT: Use non-offloading filter for proper context propagation .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() - .capturedRequestHeaders(Arrays.asList("user-agent", "x-request-id")) - .capturedResponseHeaders(Arrays.asList("content-type")) .build()) - - // Add request draining AFTER OpenTelemetry filter + // Add request draining and exception mapping AFTER OpenTelemetry filter. + // .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) + .appendNonOffloadingServiceFilter(HttpExceptionMapperServiceFilter.INSTANCE) .listen(service); ---- @@ -78,8 +79,8 @@ HttpServerBuilder.forAddress(localAddress(0)) // Capture specific request and response headers as span attributes OpenTelemetryHttpRequesterFilter filter = new OpenTelemetryHttpRequesterFilter.Builder() .componentName("my-client") - .capturedRequestHeaders(Arrays.asList("x-custom-header", "authorization")) - .capturedResponseHeaders(Arrays.asList("x-response-id", "content-type")) + .capturedRequestHeaders(Arrays.asList("content-encoding", "content-length")) + .capturedResponseHeaders(Arrays.asList("content-type")) .build(); ---- @@ -117,17 +118,21 @@ When using multiple filters on clients, place the OpenTelemetry filter appropria [source,java] ---- +OpenTelemetryHttpRequesterFilter filter = + new OpenTelemetryHttpRequesterFilter.Builder() + .componentName("my-client") + .build(); HttpClient client = HttpClients.forSingleAddress("example.com", 80) - // Logging or other observability filters before tracing - .appendClientFilter(loggingFilter) - // OpenTelemetry filter for span creation and context propagation - .appendClientFilter(new OpenTelemetryHttpRequesterFilter.Builder() - .componentName("my-client") - .build()) - - // Retry and other resilience filters after tracing + .appendClientFilter(filter) + // Adding the same filter as a connection filter will give you + // additional spans for each physical request sent over the wire + .appendConnectionFilter(filter) + // Logging comes after tracing so we can get trace-id's in logs + .appendClientFilter(loggingFilter) + // Retry and other resilience filters also come after tracing .appendClientFilter(retryFilter) + .build(); ---- @@ -167,7 +172,7 @@ ServiceTalk's async context system ensures OpenTelemetry context is maintained a * **Executor transitions** when work is offloaded * **Filter chains** where context must be preserved -This integration is provided by the `servicetalk-opentelemetry-asynccontext` module, which implements the `CapturedContextProvider` interface. +This integration is provided by the `servicetalk-opentelemetry-asynccontext` module which should be added as a `runtimeOnly` dependency. This module provides the `CapturedContextProvider` class which will be service-loaded by the ServiceTalk framework. === Context Scope Management @@ -180,7 +185,7 @@ client.request(client.get("/api")) .beforeOnSuccess(response -> { // Current span is still active here Span currentSpan = Span.current(); - currentSpan.setStatus(StatusCode.OK); + currentSpan.setAttribute(myStringAttributeKey, "attribute value"); }); // Server side: span is active during service method execution @@ -212,7 +217,7 @@ Verify OpenTelemetry is properly configured and the global OpenTelemetry instanc == Examples -For complete working examples, see the OpenTelemetry examples in the ServiceTalk examples module. +For complete working examples, see the link:{source-root}/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing[OpenTelemetry tracing examples] in the ServiceTalk examples module. == Related Documentation diff --git a/settings.gradle b/settings.gradle index a6b781b3f4..10e0c41a71 100755 --- a/settings.gradle +++ b/settings.gradle @@ -66,7 +66,7 @@ include "servicetalk-annotations", "servicetalk-examples:http:http2", "servicetalk-examples:http:jaxrs", "servicetalk-examples:http:metadata", - "servicetalk-examples:http:opentelemetry", + "servicetalk-examples:http:opentelemetry-tracing", "servicetalk-examples:http:opentracing", "servicetalk-examples:http:observer", "servicetalk-examples:http:retry", @@ -153,7 +153,7 @@ project(":servicetalk-examples:http:timeout").name = "servicetalk-examples-http- project(":servicetalk-examples:http:jaxrs").name = "servicetalk-examples-http-jaxrs" project(":servicetalk-examples:http:loadbalancer").name = "servicetalk-examples-http-loadbalancer" project(":servicetalk-examples:http:metadata").name = "servicetalk-examples-http-metadata" -project(":servicetalk-examples:http:opentelemetry").name = "servicetalk-examples-http-opentelemetry" +project(":servicetalk-examples:http:opentelemetry-tracing").name = "servicetalk-examples-http-opentelemetry-tracing" project(":servicetalk-examples:http:opentracing").name = "servicetalk-examples-http-opentracing" project(":servicetalk-examples:http:observer").name = "servicetalk-examples-http-observer" project(":servicetalk-examples:http:retry").name = "servicetalk-examples-http-retry" From 657d6f979fec47e315a933f6f5f380b2b91dca58 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Thu, 2 Oct 2025 12:51:17 -0600 Subject: [PATCH 11/13] Update lockfile --- servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile b/servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile index 60c9218ccf..fdfaec8291 100644 --- a/servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile +++ b/servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile @@ -46,7 +46,7 @@ io.opentelemetry:opentelemetry-sdk:1.48.0=compileClasspath,runtimeClasspath org.apache.logging.log4j:log4j-api:2.23.1=runtimeClasspath org.apache.logging.log4j:log4j-core:2.23.1=runtimeClasspath org.apache.logging.log4j:log4j-slf4j-impl:2.23.1=runtimeClasspath -org.jctools:jctools-core:4.0.3=runtimeClasspath +org.jctools:jctools-core:4.0.5=runtimeClasspath org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=runtimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=runtimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=runtimeClasspath From 9d8586c64b181437ded1f8934a3a1584cad53c8e Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Thu, 2 Oct 2025 12:56:44 -0600 Subject: [PATCH 12/13] Remove dead comment line --- .../docs/modules/ROOT/pages/index.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc index f1ce86c5c3..9b1955138a 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc @@ -63,7 +63,6 @@ HttpServerBuilder.forAddress(localAddress(0)) .appendNonOffloadingServiceFilter(new OpenTelemetryHttpServiceFilter.Builder() .build()) // Add request draining and exception mapping AFTER OpenTelemetry filter. - // .appendNonOffloadingServiceFilter(HttpRequestAutoDrainingServiceFilter.INSTANCE) .appendNonOffloadingServiceFilter(HttpExceptionMapperServiceFilter.INSTANCE) From 9e94b734d77d739e5ea9a3a77bfaaf5c17360a06 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Fri, 3 Oct 2025 10:31:47 -0600 Subject: [PATCH 13/13] Note that adding the filter at connection level is optional --- .../opentelemetry/tracing/OpenTelemetryTracingClient.java | 2 +- .../docs/modules/ROOT/pages/index.adoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java index 7995f6daa0..16642025e7 100644 --- a/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java +++ b/servicetalk-examples/http/opentelemetry-tracing/src/main/java/io/servicetalk/examples/http/opentelemetry/tracing/OpenTelemetryTracingClient.java @@ -61,7 +61,7 @@ public static void main(String[] args) throws Exception { // IMPORTANT: OpenTelemetry filter should be placed EARLY in the filter chain // This ensures proper span creation and context propagation for downstream filters .appendClientFilter(filter) - // Adding the filter a second time as a connection filter will enable 'physical spans' + // Optional: adding the filter a second time as a connection filter will enable 'physical spans' // where there will be a higher level 'logical' span and a sub-physical span for each // request sent over the wire. .appendConnectionFilter(filter) diff --git a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc index 9b1955138a..d74ed7c219 100644 --- a/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc +++ b/servicetalk-opentelemetry-http/docs/modules/ROOT/pages/index.adoc @@ -124,8 +124,8 @@ OpenTelemetryHttpRequesterFilter filter = HttpClient client = HttpClients.forSingleAddress("example.com", 80) // OpenTelemetry filter for span creation and context propagation .appendClientFilter(filter) - // Adding the same filter as a connection filter will give you - // additional spans for each physical request sent over the wire + // Optional: adding the same filter as a connection filter will give + // you additional spans for each physical request sent over the wire .appendConnectionFilter(filter) // Logging comes after tracing so we can get trace-id's in logs .appendClientFilter(loggingFilter)