Skip to content

Commit 67163fe

Browse files
authored
refactor: unify trace metadata and rpc handling and add MDC propagation tests (#79)
* feat: align OTel trace propagation across transport bundles and asynс flows (#80)
1 parent 59320bc commit 67163fe

28 files changed

Lines changed: 2178 additions & 509 deletions

README.md

Lines changed: 100 additions & 54 deletions
Large diffs are not rendered by default.

pom.xml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@
4343
</scm>
4444

4545
<properties>
46-
<revision>2.0.11</revision>
46+
<revision>2.0.12</revision>
4747
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
4848
<maven.compiler.source>11</maven.compiler.source>
4949
<maven.compiler.target>11</maven.compiler.target>
50+
<opentelemetry.version>1.49.0</opentelemetry.version>
51+
<opentelemetry-semconv.version>1.37.0</opentelemetry-semconv.version>
5052
</properties>
5153

5254
<modules>
@@ -67,6 +69,26 @@
6769
<artifactId>slf4j-api</artifactId>
6870
<version>1.7.36</version>
6971
</dependency>
72+
<dependency>
73+
<groupId>io.opentelemetry.semconv</groupId>
74+
<artifactId>opentelemetry-semconv</artifactId>
75+
<version>${opentelemetry-semconv.version}</version>
76+
</dependency>
77+
<dependency>
78+
<groupId>io.opentelemetry</groupId>
79+
<artifactId>opentelemetry-api</artifactId>
80+
<version>${opentelemetry.version}</version>
81+
</dependency>
82+
<dependency>
83+
<groupId>io.opentelemetry</groupId>
84+
<artifactId>opentelemetry-sdk</artifactId>
85+
<version>${opentelemetry.version}</version>
86+
</dependency>
87+
<dependency>
88+
<groupId>io.opentelemetry</groupId>
89+
<artifactId>opentelemetry-exporter-otlp</artifactId>
90+
<version>${opentelemetry.version}</version>
91+
</dependency>
7092
</dependencies>
7193
</dependencyManagement>
7294
</project>

woody-api/pom.xml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,18 @@
5555
<dependency>
5656
<groupId>io.opentelemetry</groupId>
5757
<artifactId>opentelemetry-api</artifactId>
58-
<version>1.54.1</version>
5958
</dependency>
6059
<dependency>
6160
<groupId>io.opentelemetry</groupId>
6261
<artifactId>opentelemetry-sdk</artifactId>
63-
<version>1.54.1</version>
6462
</dependency>
6563
<dependency>
6664
<groupId>io.opentelemetry</groupId>
6765
<artifactId>opentelemetry-exporter-otlp</artifactId>
68-
<version>1.54.1</version>
6966
</dependency>
7067
<dependency>
71-
<groupId>io.opentelemetry</groupId>
68+
<groupId>io.opentelemetry.semconv</groupId>
7269
<artifactId>opentelemetry-semconv</artifactId>
73-
<version>1.30.1-alpha</version>
74-
<scope>runtime</scope>
7570
</dependency>
7671
<!--Test libs-->
7772
<dependency>
Lines changed: 237 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package dev.vality.woody.api;
22

3-
import dev.vality.woody.api.trace.Span;
3+
import dev.vality.woody.api.event.CallType;
4+
import dev.vality.woody.api.proxy.InstanceMethodCaller;
5+
import dev.vality.woody.api.trace.*;
46
import org.slf4j.MDC;
57

68
import java.time.Instant;
9+
import java.util.HashSet;
10+
import java.util.Locale;
11+
import java.util.Objects;
12+
import java.util.Set;
713

814
@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
915
public class MDCUtils {
@@ -15,48 +21,248 @@ public class MDCUtils {
1521
public static final String TRACE_ID = "trace_id";
1622
public static final String PARENT_ID = "parent_id";
1723
public static final String DEADLINE = "deadline";
24+
public static final String TRACE_RPC_PREFIX = "rpc.";
25+
public static final String TRACE_RPC_CLIENT_PREFIX = TRACE_RPC_PREFIX + "client.";
26+
public static final String TRACE_RPC_SERVER_PREFIX = TRACE_RPC_PREFIX + "server.";
27+
public static final String TRACE_RPC_METADATA_SUFFIX = "metadata.";
28+
public static final String EXTENDED_MDC_PROPERTY = "woody.mdc.extended";
29+
private static final ThreadLocal<Set<String>> EXTENDED_MDC_KEYS = ThreadLocal.withInitial(HashSet::new);
30+
private static volatile boolean extendedFieldsEnabled =
31+
Boolean.parseBoolean(System.getProperty(EXTENDED_MDC_PROPERTY, "true"));
1832

19-
/**
20-
* Put span data in MDC
21-
*
22-
* @param span - service or client span
23-
*/
24-
public static void putSpanData(Span span, io.opentelemetry.api.trace.Span otelSpan) {
25-
MDC.put(SPAN_ID, span.getId() != null ? span.getId() : "");
26-
MDC.put(TRACE_ID, span.getTraceId() != null ? span.getTraceId() : "");
27-
MDC.put(PARENT_ID, span.getParentId() != null ? span.getParentId() : "");
28-
MDC.put(OTEL_TRACE_ID,
29-
otelSpan.getSpanContext().getTraceId() != null ? otelSpan.getSpanContext().getTraceId() : "");
30-
MDC.put(OTEL_SPAN_ID,
31-
otelSpan.getSpanContext().getSpanId() != null ? otelSpan.getSpanContext().getSpanId() : "");
32-
MDC.put(OTEL_TRACE_FLAGS,
33-
otelSpan.getSpanContext().getTraceFlags() != null ? otelSpan.getSpanContext().getTraceFlags().asHex() :
34-
"");
35-
if (span.hasDeadline()) {
36-
MDC.put(DEADLINE, span.getDeadline().toString());
37-
}
38-
}
39-
40-
/**
41-
* Remove span data from MDC
42-
*/
43-
public static void removeSpanData() {
33+
public static void putTraceData(TraceData traceData, ContextSpan contextSpan) {
34+
if (traceData == null || contextSpan == null || contextSpan.getSpan() == null) {
35+
removeTraceData();
36+
return;
37+
}
38+
39+
io.opentelemetry.api.trace.Span otelSpan = traceData.getOtelSpan();
40+
io.opentelemetry.api.trace.SpanContext spanContext = otelSpan != null ? otelSpan.getSpanContext() : null;
41+
42+
populateSpanIdentifiers(contextSpan.getSpan());
43+
populateOtelIdentifiers(spanContext);
44+
45+
clearExtendedEntries(false);
46+
if (isExtendedFieldsEnabled()) {
47+
populateExtendedFields(traceData);
48+
}
49+
50+
updateDeadlineEntries(traceData, contextSpan);
51+
}
52+
53+
private static void populateSpanIdentifiers(Span span) {
54+
putMdcValue(SPAN_ID, span.getId());
55+
putMdcValue(TRACE_ID, span.getTraceId());
56+
putMdcValue(PARENT_ID, span.getParentId());
57+
}
58+
59+
private static void populateOtelIdentifiers(io.opentelemetry.api.trace.SpanContext spanContext) {
60+
if (spanContext == null) {
61+
putMdcValue(OTEL_TRACE_ID, null);
62+
putMdcValue(OTEL_SPAN_ID, null);
63+
putMdcValue(OTEL_TRACE_FLAGS, null);
64+
return;
65+
}
66+
putMdcValue(OTEL_TRACE_ID, spanContext.getTraceId());
67+
putMdcValue(OTEL_SPAN_ID, spanContext.getSpanId());
68+
putMdcValue(OTEL_TRACE_FLAGS,
69+
spanContext.getTraceFlags() != null ? spanContext.getTraceFlags().asHex() : null);
70+
}
71+
72+
public static void removeTraceData() {
4473
MDC.remove(SPAN_ID);
4574
MDC.remove(TRACE_ID);
4675
MDC.remove(OTEL_TRACE_ID);
4776
MDC.remove(OTEL_SPAN_ID);
4877
MDC.remove(OTEL_TRACE_FLAGS);
4978
MDC.remove(DEADLINE);
79+
clearExtendedEntries(true);
5080
}
5181

52-
public static void putDeadline(Instant deadline) {
53-
if (deadline != null) {
54-
MDC.put(DEADLINE, deadline.toString());
82+
public static void putDeadline(TraceData traceData, ContextSpan contextSpan, Instant deadline) {
83+
if (deadline == null) {
84+
removeDeadline(traceData, contextSpan);
85+
return;
5586
}
87+
88+
updateDeadlineEntries(traceData, contextSpan);
5689
}
5790

58-
public static void removeDeadline() {
59-
MDC.remove(DEADLINE);
91+
public static void removeDeadline(TraceData traceData, ContextSpan contextSpan) {
92+
updateDeadlineEntries(traceData, contextSpan);
93+
}
94+
95+
public static void enableExtendedFields() {
96+
extendedFieldsEnabled = true;
97+
}
98+
99+
public static void disableExtendedFields() {
100+
extendedFieldsEnabled = false;
101+
clearExtendedEntries(false);
102+
}
103+
104+
public static boolean isExtendedFieldsEnabled() {
105+
return extendedFieldsEnabled;
106+
}
107+
108+
private static void populateExtendedFields(TraceData traceData) {
109+
addSpanDetails(traceData.getClientSpan(), TRACE_RPC_CLIENT_PREFIX);
110+
addSpanDetails(traceData.getServiceSpan(), TRACE_RPC_SERVER_PREFIX);
111+
}
112+
113+
private static void addSpanDetails(ContextSpan contextSpan, String prefix) {
114+
if (contextSpan == null || !contextSpan.isFilled()) {
115+
return;
116+
}
117+
118+
addExtendedEntry(prefix + "service", resolveServiceName(contextSpan));
119+
addExtendedEntry(prefix + "function", resolveFunctionName(contextSpan));
120+
addExtendedEntry(prefix + "type", resolveCallType(contextSpan));
121+
addExtendedEntry(prefix + "event", resolveEvent(contextSpan));
122+
addExtendedEntry(prefix + "url", resolveEndpoint(contextSpan));
123+
124+
long duration = contextSpan.getSpan().getDuration();
125+
if (duration > 0) {
126+
addExtendedEntry(prefix + "execution_duration_ms", Long.toString(duration));
127+
}
128+
129+
addCustomMetadataEntries(contextSpan, prefix + TRACE_RPC_METADATA_SUFFIX);
130+
}
131+
132+
private static void addCustomMetadataEntries(ContextSpan contextSpan, String prefix) {
133+
Metadata metadata = contextSpan.getCustomMetadata();
134+
if (metadata == null) {
135+
return;
136+
}
137+
for (String key : metadata.getKeys()) {
138+
Object value = metadata.getValue(key);
139+
if (value != null) {
140+
addExtendedEntry(prefix + key, Objects.toString(value));
141+
}
142+
}
143+
}
144+
145+
private static String resolveServiceName(ContextSpan contextSpan) {
146+
InstanceMethodCaller caller = contextSpan.getMetadata().getValue(MetadataProperties.INSTANCE_METHOD_CALLER);
147+
if (caller == null || caller.getTargetMethod() == null) {
148+
return null;
149+
}
150+
Class<?> declaringClass = caller.getTargetMethod().getDeclaringClass();
151+
if (declaringClass == null) {
152+
return null;
153+
}
154+
Class<?> serviceClass = declaringClass;
155+
if (declaringClass.getEnclosingClass() != null) {
156+
String simple = declaringClass.getSimpleName();
157+
if ("Iface".equals(simple) || "AsyncIface".equals(simple)) {
158+
serviceClass = declaringClass.getEnclosingClass();
159+
}
160+
}
161+
String simpleName = serviceClass.getSimpleName();
162+
if (simpleName.endsWith("Srv")) {
163+
simpleName = simpleName.substring(0, simpleName.length() - 3);
164+
}
165+
return simpleName;
166+
}
167+
168+
private static String resolveFunctionName(ContextSpan contextSpan) {
169+
String callName = contextSpan.getMetadata().getValue(MetadataProperties.CALL_NAME);
170+
if (callName != null && !callName.isEmpty()) {
171+
return callName;
172+
}
173+
InstanceMethodCaller caller = contextSpan.getMetadata().getValue(MetadataProperties.INSTANCE_METHOD_CALLER);
174+
if (caller != null && caller.getTargetMethod() != null) {
175+
return caller.getTargetMethod().getName();
176+
}
177+
return null;
178+
}
179+
180+
private static String resolveCallType(ContextSpan contextSpan) {
181+
CallType callType = contextSpan.getMetadata().getValue(MetadataProperties.CALL_TYPE);
182+
if (callType != null) {
183+
return callType.name().toLowerCase(Locale.ROOT);
184+
}
185+
return null;
186+
}
187+
188+
private static String resolveEvent(ContextSpan contextSpan) {
189+
Object event = contextSpan.getMetadata().getValue(MetadataProperties.EVENT_TYPE);
190+
if (event instanceof Enum<?>) {
191+
return formatEnum((Enum<?>) event);
192+
}
193+
return null;
194+
}
195+
196+
private static String resolveEndpoint(ContextSpan contextSpan) {
197+
Object endpoint = contextSpan.getMetadata().getValue(MetadataProperties.CALL_ENDPOINT);
198+
if (endpoint instanceof Endpoint) {
199+
return ((Endpoint<?>) endpoint).getStringValue();
200+
}
201+
return endpoint != null ? endpoint.toString() : null;
202+
}
203+
204+
private static String formatEnum(Enum<?> value) {
205+
return value == null ? null : value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
206+
}
207+
208+
private static void addExtendedEntry(String key, String value) {
209+
if (key == null || value == null || value.isEmpty()) {
210+
return;
211+
}
212+
MDC.put(key, value);
213+
EXTENDED_MDC_KEYS.get().add(key);
214+
}
215+
216+
private static void putMdcValue(String key, String value) {
217+
MDC.put(key, value != null ? value : "");
218+
}
219+
220+
private static void removeExtendedEntry(String key) {
221+
MDC.remove(key);
222+
EXTENDED_MDC_KEYS.get().remove(key);
223+
}
224+
225+
private static void updateDeadlineEntries(TraceData traceData, ContextSpan contextSpan) {
226+
Instant activeDeadline = contextSpan != null ? ContextUtils.getDeadline(contextSpan) : null;
227+
if (activeDeadline != null) {
228+
MDC.put(DEADLINE, activeDeadline.toString());
229+
} else {
230+
MDC.remove(DEADLINE);
231+
}
232+
233+
removeExtendedEntry(TRACE_RPC_CLIENT_PREFIX + "deadline");
234+
removeExtendedEntry(TRACE_RPC_SERVER_PREFIX + "deadline");
235+
236+
if (!isExtendedFieldsEnabled()) {
237+
return;
238+
}
239+
240+
if (traceData != null) {
241+
addDeadlineEntry(traceData.getClientSpan(), TRACE_RPC_CLIENT_PREFIX);
242+
addDeadlineEntry(traceData.getServiceSpan(), TRACE_RPC_SERVER_PREFIX);
243+
}
244+
}
245+
246+
private static void addDeadlineEntry(ContextSpan span, String prefix) {
247+
if (span == null) {
248+
return;
249+
}
250+
Instant deadline = ContextUtils.getDeadline(span);
251+
if (deadline != null) {
252+
addExtendedEntry(prefix + "deadline", deadline.toString());
253+
}
60254
}
61255

256+
private static void clearExtendedEntries(boolean removeThreadLocal) {
257+
Set<String> keys = EXTENDED_MDC_KEYS.get();
258+
for (String key : keys) {
259+
MDC.remove(key);
260+
}
261+
262+
if (removeThreadLocal) {
263+
EXTENDED_MDC_KEYS.remove();
264+
} else {
265+
keys.clear();
266+
}
267+
}
62268
}

0 commit comments

Comments
 (0)