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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/generation/site-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ content:
- url: ../../
branches: HEAD
start_path: servicetalk-traffic-resilience-http/docs
- url: ../../
branches: HEAD
start_path: servicetalk-opentelemetry-http/docs
asciidoc:
attributes:
experimental: ''
Expand Down
4 changes: 4 additions & 0 deletions docs/generation/site-remote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ content:
branches: main
tags: [0.42.60]
start_path: servicetalk-traffic-resilience-http/docs
- url: https://github.com/apple/servicetalk.git
branches: main
tags: []
start_path: servicetalk-opentelemetry-http/docs
asciidoc:
attributes:
experimental: ''
Expand Down
6 changes: 6 additions & 0 deletions docs/modules/ROOT/pages/_partials/nav-versioned.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,11 @@ 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-http:ROOT:partial$nav-versioned.adoc[]
--
+
* xref:{page-version}@servicetalk::javadoc/index.adoc[Javadoc]
* xref:{page-version}@servicetalk::contributing.adoc[Contributing]
Original file line number Diff line number Diff line change
Expand Up @@ -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#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]
Expand Down
15 changes: 15 additions & 0 deletions servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

[#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 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-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

Expand Down
38 changes: 38 additions & 0 deletions servicetalk-examples/http/opentelemetry-tracing/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 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

runtimeOnly project(":servicetalk-opentelemetry-asynccontext") // OpenTelemetry async context propagation

// OpenTelemetry Java SDK
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"
}
56 changes: 56 additions & 0 deletions servicetalk-examples/http/opentelemetry-tracing/gradle.lockfile
Original file line number Diff line number Diff line change
@@ -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.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
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.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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.tracing;

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:
* <ul>
* <li>Configure OpenTelemetry SDK with logging exporter</li>
* <li>Set up ServiceTalk HTTP client with OpenTelemetry tracing filter</li>
* <li>Demonstrate proper client-side filter ordering</li>
* <li>Automatically capture HTTP request/response spans</li>
* <li>Propagate trace context to downstream services</li>
* </ul>
*/
public final class OpenTelemetryTracingClient {
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);

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(filter)
// 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)
.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));

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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.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;

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;

/**
* A server that demonstrates OpenTelemetry distributed tracing with ServiceTalk.
* This example shows how to:
* <ul>
* <li>Configure OpenTelemetry SDK with logging exporter</li>
* <li>Set up ServiceTalk HTTP server with OpenTelemetry tracing filter</li>
* <li>Demonstrate proper filter ordering for accurate tracing</li>
* <li>Automatically capture HTTP request/response spans</li>
* </ul>
*/
public final class OpenTelemetryTracingServer {
private static final Logger LOGGER = LoggerFactory.getLogger(OpenTelemetryTracingServer.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);

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 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)
// 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
.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();
}
}
Original file line number Diff line number Diff line change
@@ -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.tracing;

import io.servicetalk.annotations.ElementsAreNonnullByDefault;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<Configuration status="info">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %30t [%-5level] %-15logger{1} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<!-- Use `-Dservicetalk.logger.level=DEBUG` to change the root logger level via command line -->
<Root level="${sys:servicetalk.logger.level:-INFO}">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Loading