diff --git a/rest-client-base/pom.xml b/rest-client-base/pom.xml
index 8314aa9..c62c94b 100644
--- a/rest-client-base/pom.xml
+++ b/rest-client-base/pom.xml
@@ -61,6 +61,12 @@
netty-http-authenticator
1.5
+
+
+ io.opentelemetry
+ opentelemetry-api
+
+
diff --git a/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java
index e89b7e0..1a1741a 100644
--- a/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java
+++ b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java
@@ -212,6 +212,7 @@ private void initializeWebClient() throws RestClientException {
});
});
}
+ builder.filter(TraceparentFilterFunction.handleTraceparentContext());
final ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
webClient = builder.baseUrl(config.getBaseUrl()).clientConnector(connector).build();
diff --git a/rest-client-base/src/main/java/com/wultra/core/rest/client/base/TraceparentFilterFunction.java b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/TraceparentFilterFunction.java
new file mode 100644
index 0000000..46a501e
--- /dev/null
+++ b/rest-client-base/src/main/java/com/wultra/core/rest/client/base/TraceparentFilterFunction.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 Wultra s.r.o.
+ *
+ * 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 com.wultra.core.rest.client.base;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.TraceFlags;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.ExchangeFunction;
+import reactor.core.publisher.Mono;
+
+/**
+ * An ExchangeFilterFunction for adding traceparent header to WebClient requests.
+ * It also propagates trace ID through Reactor context and MDC for logging purposes.
+ *
+ * @author Jan Dusil, jan.dusil@wultra.com
+ */
+public class TraceparentFilterFunction implements ExchangeFilterFunction {
+
+ private static final String TRACEPARENT_HEADER_KEY = "traceparent";
+
+ /**
+ * Applies the filter to the given ClientRequest and ExchangeFunction.
+ *
+ * @param request The client request.
+ * @param next The next exchange function in the chain.
+ * @return a Mono after applying the filter.
+ */
+ @Override
+ public Mono filter(final ClientRequest request, final ExchangeFunction next) {
+ return next.exchange(addTraceparentHeader(request));
+ }
+
+ /**
+ * Adds a traceparent header to the ClientRequest if a current span context is available.
+ *
+ * @param request The client request.
+ * @return a modified ClientRequest with the traceparent header added.
+ */
+ private ClientRequest addTraceparentHeader(final ClientRequest request) {
+ final Span currentSpan = Span.current();
+ if (currentSpan != null) {
+ final SpanContext spanContext = currentSpan.getSpanContext();
+ if (spanContext != null) {
+ final String traceId = spanContext.getTraceId();
+ /* The parentId of the next server Span will be the current spanId */
+ final String parentId = spanContext.getSpanId();
+ final TraceFlags traceFlags = spanContext.getTraceFlags();
+ if (traceId != null && parentId != null && traceFlags != null) {
+ final String headerValue = String.format("00-%s-%s-%s",
+ traceId,
+ parentId,
+ traceFlags.asHex());
+ return ClientRequest.from(request)
+ .headers(headers -> headers.set(TRACEPARENT_HEADER_KEY, headerValue))
+ .build();
+ }
+ }
+ }
+ return request;
+ }
+
+ /**
+ * Factory method to create an instance of TraceparentFilterFunction.
+ *
+ * @return a new instance of TraceparentFilterFunction.
+ */
+ public static ExchangeFilterFunction handleTraceparentContext() {
+ return new TraceparentFilterFunction();
+ }
+}