Skip to content

Commit a333ff8

Browse files
authored
[8.5] Add trace.id to request trace logs (#91772) (#91859)
* Add trace.id to request trace logs (#91772) This adds trace.id, extracted from traceparent header, so its present in the request trace log. It is already included in response trace logs via the threadcontext, but that is not set at request log time.
1 parent eb032b9 commit a333ff8

File tree

5 files changed

+121
-5
lines changed

5 files changed

+121
-5
lines changed

docs/changelog/91772.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 91772
2+
summary: Add `trace.id` to request trace logs
3+
area: Infra/Core
4+
type: bug
5+
issues: [88174]

server/src/main/java/org/elasticsearch/http/HttpTracer.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.core.Nullable;
1717
import org.elasticsearch.rest.RestRequest;
1818
import org.elasticsearch.rest.RestResponse;
19+
import org.elasticsearch.rest.RestUtils;
1920
import org.elasticsearch.tasks.Task;
2021
import org.elasticsearch.transport.TransportService;
2122

@@ -33,6 +34,11 @@ class HttpTracer {
3334
private volatile String[] tracerLogInclude;
3435
private volatile String[] tracerLogExclude;
3536

37+
// for testing
38+
HttpTracer() {
39+
tracerLogInclude = tracerLogExclude = new String[0];
40+
}
41+
3642
HttpTracer(Settings settings, ClusterSettings clusterSettings) {
3743

3844
setTracerLogInclude(HttpTransportSettings.SETTING_HTTP_TRACE_LOG_INCLUDE.get(settings));
@@ -55,14 +61,17 @@ class HttpTracer {
5561
@Nullable
5662
HttpTracer maybeLogRequest(RestRequest restRequest, @Nullable Exception e) {
5763
if (logger.isTraceEnabled() && TransportService.shouldTraceAction(restRequest.uri(), tracerLogInclude, tracerLogExclude)) {
64+
// trace.id in the response log is included from threadcontext, which isn't set at request log time
65+
// so include it here as part of the message
5866
logger.trace(
5967
() -> format(
60-
"[%s][%s][%s][%s] received request from [%s]",
68+
"[%s][%s][%s][%s] received request from [%s]%s",
6169
restRequest.getRequestId(),
6270
restRequest.header(Task.X_OPAQUE_ID_HTTP_HEADER),
6371
restRequest.method(),
6472
restRequest.uri(),
65-
restRequest.getHttpChannel()
73+
restRequest.getHttpChannel(),
74+
RestUtils.extractTraceId(restRequest.header(Task.TRACE_PARENT_HTTP_HEADER)).map(t -> " trace.id: " + t).orElse("")
6675
),
6776
e
6877
);
@@ -89,6 +98,7 @@ void logResponse(
8998
long requestId,
9099
boolean success
91100
) {
101+
// trace id is included in the ThreadContext for the response
92102
logger.trace(
93103
() -> format(
94104
"[%s][%s][%s][%s][%s] sent response to [%s] success [%s]",

server/src/main/java/org/elasticsearch/rest/RestController.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.List;
4444
import java.util.Locale;
4545
import java.util.Map;
46+
import java.util.Optional;
4647
import java.util.Set;
4748
import java.util.concurrent.atomic.AtomicBoolean;
4849
import java.util.function.Supplier;
@@ -560,8 +561,9 @@ private void copyRestHeaders(RestRequest request, ThreadContext threadContext) {
560561
throw new IllegalArgumentException("multiple values for single-valued header [" + name + "].");
561562
} else if (name.equals(Task.TRACE_PARENT_HTTP_HEADER)) {
562563
String traceparent = distinctHeaderValues.get(0);
563-
if (traceparent.length() >= 55) {
564-
threadContext.putHeader(Task.TRACE_ID, traceparent.substring(3, 35));
564+
Optional<String> traceId = RestUtils.extractTraceId(traceparent);
565+
if (traceId.isPresent()) {
566+
threadContext.putHeader(Task.TRACE_ID, traceId.get());
565567
threadContext.putTransient("parent_" + Task.TRACE_PARENT_HTTP_HEADER, traceparent);
566568
}
567569
} else if (name.equals(Task.TRACE_STATE)) {

server/src/main/java/org/elasticsearch/rest/RestUtils.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.nio.charset.StandardCharsets;
1717
import java.util.Arrays;
1818
import java.util.Map;
19+
import java.util.Optional;
1920
import java.util.regex.Pattern;
2021

2122
public class RestUtils {
@@ -236,6 +237,18 @@ public static String[] corsSettingAsArray(String corsSetting) {
236237
if (Strings.isNullOrEmpty(corsSetting)) {
237238
return new String[0];
238239
}
239-
return Arrays.asList(corsSetting.split(",")).stream().map(String::trim).toArray(size -> new String[size]);
240+
return Arrays.stream(corsSetting.split(",")).map(String::trim).toArray(String[]::new);
240241
}
242+
243+
/**
244+
* Extract the trace id from the specified traceparent string.
245+
* @see <a href="https://www.w3.org/TR/trace-context/#traceparent-header">W3 traceparent spec</a>
246+
*
247+
* @param traceparent The value from the {@code traceparent} HTTP header
248+
* @return The trace id from the traceparent string, or {@code Optional.empty()} if it is not present.
249+
*/
250+
public static Optional<String> extractTraceId(String traceparent) {
251+
return traceparent != null && traceparent.length() >= 55 ? Optional.of(traceparent.substring(3, 35)) : Optional.empty();
252+
}
253+
241254
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.http;
10+
11+
import org.apache.logging.log4j.Level;
12+
import org.apache.logging.log4j.LogManager;
13+
import org.elasticsearch.common.logging.Loggers;
14+
import org.elasticsearch.rest.RestRequest;
15+
import org.elasticsearch.rest.RestResponse;
16+
import org.elasticsearch.rest.RestStatus;
17+
import org.elasticsearch.tasks.Task;
18+
import org.elasticsearch.test.ESTestCase;
19+
import org.elasticsearch.test.MockLogAppender;
20+
import org.elasticsearch.test.junit.annotations.TestLogging;
21+
import org.elasticsearch.test.rest.FakeRestRequest;
22+
import org.elasticsearch.xcontent.NamedXContentRegistry;
23+
24+
import java.net.InetSocketAddress;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
public class HttpTracerTests extends ESTestCase {
29+
30+
@TestLogging(reason = "Get HttpTracer to output trace logs", value = "org.elasticsearch.http.HttpTracer:TRACE")
31+
public void testLogging() throws IllegalAccessException {
32+
String httpTracerLog = HttpTracer.class.getName();
33+
34+
MockLogAppender appender = new MockLogAppender();
35+
try {
36+
appender.start();
37+
Loggers.addAppender(LogManager.getLogger(httpTracerLog), appender);
38+
39+
appender.addExpectation(
40+
new MockLogAppender.PatternSeenEventExpectation(
41+
"request log",
42+
httpTracerLog,
43+
Level.TRACE,
44+
"\\[\\d+]\\[idHeader]\\[GET]\\[uri] received request from \\[.*] trace.id: 4bf92f3577b34da6a3ce929d0e0e4736"
45+
)
46+
);
47+
appender.addExpectation(
48+
new MockLogAppender.PatternSeenEventExpectation(
49+
"response log",
50+
httpTracerLog,
51+
Level.TRACE,
52+
"\\[\\d+]\\[idHeader]\\[ACCEPTED]\\[text/plain; charset=UTF-8]\\[length] sent response to \\[.*] success \\[true]"
53+
)
54+
);
55+
56+
RestRequest request = new FakeRestRequest.Builder(new NamedXContentRegistry(List.of())).withMethod(RestRequest.Method.GET)
57+
.withPath("uri")
58+
.withHeaders(
59+
Map.of(
60+
Task.X_OPAQUE_ID_HTTP_HEADER,
61+
List.of("idHeader"),
62+
"traceparent",
63+
List.of("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
64+
)
65+
)
66+
.build();
67+
68+
HttpTracer tracer = new HttpTracer().maybeLogRequest(request, null);
69+
assertNotNull(tracer);
70+
71+
tracer.logResponse(
72+
new RestResponse(RestStatus.ACCEPTED, ""),
73+
new FakeRestRequest.FakeHttpChannel(InetSocketAddress.createUnresolved("127.0.0.1", 9200)),
74+
"length",
75+
"idHeader",
76+
1L,
77+
true
78+
);
79+
80+
appender.assertAllExpectationsMatched();
81+
} finally {
82+
Loggers.removeAppender(LogManager.getLogger(httpTracerLog), appender);
83+
appender.stop();
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)