Skip to content

Commit b784d23

Browse files
committed
add X-Trace-Id and traceparent headers to all HTTP calls
this will allow for log correlation from CDA for all logs emmitted from a single request
1 parent c4234a6 commit b784d23

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

cwms-http-client/src/main/java/mil/army/usace/hec/cwms/http/client/OkHttpClientInstance.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ static OkHttpClient createClient() {
7171
.readTimeout(getReadTimeout())
7272
.writeTimeout(getWriteTimeout())
7373
.addInterceptor(LOGGING_INTERCEPTOR)
74+
.addInterceptor(new TraceHeadersInterceptor())
7475
.cache(getCache())
7576
.build();
7677
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2026
3+
* United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC)
4+
* All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL.
5+
* Source may not be released without written approval from HEC
6+
*/
7+
8+
package mil.army.usace.hec.cwms.http.client;
9+
10+
import java.io.IOException;
11+
import java.util.UUID;
12+
import okhttp3.Interceptor;
13+
import okhttp3.Request;
14+
import okhttp3.Response;
15+
16+
final class TraceHeadersInterceptor implements Interceptor {
17+
18+
private static final String TRACE_PARENT_HEADER = "traceparent";
19+
private static final String TRACE_ID_HEADER = "X-Trace-Id";
20+
21+
@Override
22+
public Response intercept(Chain chain) throws IOException {
23+
Request request = chain.request();
24+
25+
String traceId = generateTraceId();
26+
String traceParent = createTraceParent(traceId);
27+
28+
Request tracedRequest = request.newBuilder()
29+
.header(TRACE_ID_HEADER, traceId)
30+
.header(TRACE_PARENT_HEADER, traceParent)
31+
.build();
32+
33+
return chain.proceed(tracedRequest);
34+
}
35+
36+
private static String generateTraceId() {
37+
return UUID.randomUUID().toString().toLowerCase();
38+
}
39+
40+
private static String createTraceParent(String traceId) {
41+
String traceIdHex = traceId.replace("-", "");
42+
String spanId = UUID.randomUUID().toString().replace("-", "").substring(0, 16).toLowerCase();
43+
return "00-" + traceIdHex + "-" + spanId + "-01";
44+
}
45+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2026
3+
* United States Army Corps of Engineers - Hydrologic Engineering Center (USACE/HEC)
4+
* All Rights Reserved. USACE PROPRIETARY/CONFIDENTIAL.
5+
* Source may not be released without written approval from HEC
6+
*/
7+
8+
package mil.army.usace.hec.cwms.http.client;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertFalse;
12+
import static org.junit.jupiter.api.Assertions.assertNotNull;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
15+
import java.io.IOException;
16+
import java.util.regex.Pattern;
17+
import okhttp3.Request;
18+
import okhttp3.Response;
19+
import okhttp3.mockwebserver.MockResponse;
20+
import okhttp3.mockwebserver.MockWebServer;
21+
import org.junit.jupiter.api.Test;
22+
23+
final class TraceHeadersInterceptorTest {
24+
25+
private static final Pattern TRACE_ID_PATTERN =
26+
Pattern.compile("[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}", Pattern.CASE_INSENSITIVE);
27+
28+
29+
@Test
30+
void testCookiesNotLogged() throws IOException, InterruptedException {
31+
try(MockWebServer mockWebServer = new MockWebServer()) {
32+
MockResponse mockResponse = new MockResponse()
33+
.setBody("test")
34+
.setResponseCode(200);
35+
mockWebServer.enqueue(mockResponse);
36+
mockWebServer.start();
37+
38+
Request request = new Request.Builder()
39+
.url(mockWebServer.url("/test"))
40+
.build();
41+
try (Response response = OkHttpClientInstance.getInstance().newCall(request).execute()) {
42+
assertTrue(response.isSuccessful());
43+
}
44+
45+
okhttp3.mockwebserver.RecordedRequest recordedRequest = mockWebServer.takeRequest();
46+
String traceId = recordedRequest.getHeader("X-Trace-Id");
47+
String traceParent = recordedRequest.getHeader("traceparent");
48+
49+
assertTrue(traceId != null && TRACE_ID_PATTERN.matcher(traceId).matches(),
50+
"X-Trace-Id should be a UUID-like value");
51+
assertTrue(traceParent != null && traceParent.matches(
52+
"00-[a-f0-9]{32}-[a-f0-9]{16}-[a-f0-9]{2}"),
53+
"traceparent should follow W3C format: https://www.w3.org/TR/trace-context/#traceparent-header");
54+
assertTrue(traceParent.startsWith("00-"));
55+
assertEquals(4, traceParent.split("-").length);
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)