Skip to content

Commit c0da4f5

Browse files
fix: propagate tracing context to downstream calls (#19)
1 parent fb69118 commit c0da4f5

File tree

6 files changed

+81
-4
lines changed

6 files changed

+81
-4
lines changed

hypertrace-core-graphql-context/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies {
1010
api("com.graphql-java-kickstart:graphql-java-servlet")
1111

1212
implementation(project(":hypertrace-core-graphql-spi"))
13+
implementation("com.google.guava:guava")
1314

1415
testImplementation("org.junit.jupiter:junit-jupiter")
1516
testImplementation("org.mockito:mockito-core")

hypertrace-core-graphql-context/src/main/java/org/hypertrace/core/graphql/context/DefaultGraphQlRequestContextBuilder.java

+19
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package org.hypertrace.core.graphql.context;
22

3+
import com.google.common.collect.Streams;
34
import com.google.inject.Injector;
45
import graphql.kickstart.servlet.context.DefaultGraphQLServletContext;
56
import graphql.kickstart.servlet.context.DefaultGraphQLServletContextBuilder;
67
import graphql.kickstart.servlet.context.GraphQLServletContext;
78
import graphql.schema.DataFetcher;
89
import java.util.Arrays;
10+
import java.util.Map;
911
import java.util.Optional;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
1014
import javax.annotation.Nonnull;
1115
import javax.inject.Inject;
1216
import javax.security.auth.Subject;
@@ -20,6 +24,8 @@ class DefaultGraphQlRequestContextBuilder extends DefaultGraphQLServletContextBu
2024
private static final String DEFAULT_CONTEXT_ID = "DEFAULT_CONTEXT_ID";
2125
static final String AUTHORIZATION_HEADER_KEY = "Authorization";
2226
static final String TENANT_ID_HEADER_KEY = "x-tenant-id";
27+
static final Set<String> TRACING_CONTEXT_HEADER_KEY_PREFIXES =
28+
Set.of("X-B3-", "traceparent", "tracestate");
2329

2430
private final Injector injector;
2531
private final GraphQlServiceConfig serviceConfig;
@@ -75,6 +81,19 @@ public Optional<String> getTenantId() {
7581
.or(DefaultGraphQlRequestContextBuilder.this.serviceConfig::getDefaultTenantId);
7682
}
7783

84+
@Override
85+
public Map<String, String> getTracingContextHeaders() {
86+
return Streams.stream(
87+
this.servletContext.getHttpServletRequest().getHeaderNames().asIterator())
88+
.filter(
89+
header ->
90+
TRACING_CONTEXT_HEADER_KEY_PREFIXES.stream()
91+
.anyMatch(prefix -> header.toLowerCase().startsWith(prefix.toLowerCase())))
92+
.collect(
93+
Collectors.toUnmodifiableMap(
94+
String::toLowerCase, this.servletContext.getHttpServletRequest()::getHeader));
95+
}
96+
7897
@Nonnull
7998
@Override
8099
public ContextualCachingKey getCachingKey() {

hypertrace-core-graphql-context/src/main/java/org/hypertrace/core/graphql/context/GraphQlRequestContext.java

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import graphql.kickstart.execution.context.GraphQLContext;
44
import graphql.schema.DataFetcher;
5+
import java.util.Map;
56
import java.util.Optional;
67
import javax.annotation.Nonnull;
78

@@ -17,6 +18,8 @@ public interface GraphQlRequestContext extends GraphQLContext {
1718

1819
Optional<String> getTenantId();
1920

21+
Map<String, String> getTracingContextHeaders();
22+
2023
@Nonnull
2124
ContextualCachingKey getCachingKey();
2225
}

hypertrace-core-graphql-context/src/test/java/org/hypertrace/core/graphql/context/DefaultGraphQlRequestContextBuilderTest.java

+32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.hypertrace.core.graphql.context;
22

3+
import static java.util.Collections.emptyMap;
34
import static org.hypertrace.core.graphql.context.DefaultGraphQlRequestContextBuilder.AUTHORIZATION_HEADER_KEY;
45
import static org.hypertrace.core.graphql.context.DefaultGraphQlRequestContextBuilder.TENANT_ID_HEADER_KEY;
56
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -15,6 +16,9 @@
1516

1617
import com.google.inject.Injector;
1718
import graphql.schema.DataFetcher;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Map;
1822
import java.util.Optional;
1923
import javax.servlet.http.HttpServletRequest;
2024
import javax.servlet.http.HttpServletResponse;
@@ -103,4 +107,32 @@ void returnsCachingKeysEqualForSameTenant() {
103107
var thirdKey = this.contextBuilder.build(this.mockRequest, this.mockResponse).getCachingKey();
104108
assertNotEquals(firstKey, thirdKey);
105109
}
110+
111+
@Test
112+
void returnsEmptyMapIfNoTracingHeadersPresent() {
113+
when(this.mockRequest.getHeaderNames()).thenReturn(Collections.enumeration(List.of("foo")));
114+
assertEquals(emptyMap(), this.requestContext.getTracingContextHeaders());
115+
}
116+
117+
@Test
118+
void returnsLowerCasedTracingHeadersIfAnyMatches() {
119+
when(this.mockRequest.getHeaderNames())
120+
.thenReturn(
121+
Collections.enumeration(
122+
List.of(
123+
"traceSTATE", "traceparent", "other", "X-B3-traceid", "x-b3-parent-trace-id")));
124+
when(this.mockRequest.getHeader(any(String.class)))
125+
.thenAnswer(invocation -> invocation.getArgument(0) + " value");
126+
assertEquals(
127+
Map.of(
128+
"tracestate",
129+
"traceSTATE value",
130+
"traceparent",
131+
"traceparent value",
132+
"x-b3-traceid",
133+
"X-B3-traceid value",
134+
"x-b3-parent-trace-id",
135+
"x-b3-parent-trace-id value"),
136+
this.requestContext.getTracingContextHeaders());
137+
}
106138
}

hypertrace-core-graphql-grpc-utils/src/main/java/org/hypertrace/core/graphql/utils/grpc/DefaultGraphQlGrpcContextBuilder.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import io.grpc.Context;
77
import io.grpc.stub.StreamObserver;
88
import io.reactivex.rxjava3.core.Observable;
9+
import java.util.Arrays;
10+
import java.util.Collection;
911
import java.util.Map;
1012
import java.util.Map.Entry;
1113
import java.util.Optional;
@@ -37,10 +39,12 @@ private DefaultGraphQlGrpcContext(
3739
GraphQlRequestContext requestContext,
3840
PlatformRequestContextBuilder platformRequestContextBuilder) {
3941
Map<String, String> grpcHeaders =
40-
this.flattenOptionalMap(
41-
Map.of(
42-
AUTHORIZATION_HEADER, this.extractAuthorizationHeader(requestContext),
43-
TENANT_ID_HEADER_KEY, this.extractTenantId(requestContext)));
42+
this.mergeMaps(
43+
requestContext.getTracingContextHeaders(),
44+
this.flattenOptionalMap(
45+
Map.of(
46+
AUTHORIZATION_HEADER, this.extractAuthorizationHeader(requestContext),
47+
TENANT_ID_HEADER_KEY, this.extractTenantId(requestContext))));
4448

4549
RequestContext platformContext = platformRequestContextBuilder.build(grpcHeaders);
4650
this.grpcContext = this.buildGrpcContext(platformContext);
@@ -87,5 +91,13 @@ private Map<String, String> flattenOptionalMap(Map<String, Optional<String>> opt
8791
.flatMap(Optional::stream)
8892
.collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue));
8993
}
94+
95+
@SafeVarargs
96+
private Map<String, String> mergeMaps(Map<String, String>... maps) {
97+
return Arrays.stream(maps)
98+
.map(Map::entrySet)
99+
.flatMap(Collection::stream)
100+
.collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue));
101+
}
90102
}
91103
}

hypertrace-core-graphql-grpc-utils/src/test/java/org/hypertrace/core/graphql/utils/grpc/DefaultGraphQlGrpcContextBuilderTest.java

+10
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,14 @@ void addsTenantIdToContext() {
140140
this.builder.build(this.mockRequestContext);
141141
verify(this.mockPlatformRequestContextBuilder).build(Map.of(TENANT_ID_HEADER_KEY, "tenant id"));
142142
}
143+
144+
@Test
145+
void addsTracingHeadersToContext() {
146+
when(this.mockRequestContext.getTenantId()).thenReturn(Optional.of("tenant id"));
147+
when(this.mockRequestContext.getTracingContextHeaders())
148+
.thenReturn(Map.of("traceid", "traceid value"));
149+
this.builder.build(this.mockRequestContext);
150+
verify(this.mockPlatformRequestContextBuilder)
151+
.build(Map.of(TENANT_ID_HEADER_KEY, "tenant id", "traceid", "traceid value"));
152+
}
143153
}

0 commit comments

Comments
 (0)