Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7dd9601
Add for user agent
OStevan Oct 21, 2021
34ccde0
Fix isolation
OStevan Oct 21, 2021
c7ecafa
Sort out method addition
OStevan Oct 21, 2021
ca6e1f2
Okhttp interceptor
OStevan Oct 21, 2021
537df0a
revert Tracers
OStevan Oct 21, 2021
5214eba
Add generated changelog entries
svc-changelog Oct 21, 2021
3fd79e6
For user agent name change
OStevan Oct 21, 2021
044a98d
Overloaded delegate
OStevan Oct 21, 2021
2438b30
Remove `FOR_USER_AGENT_PROPERTY_NAME`
OStevan Oct 21, 2021
fd9356d
Cascading for user agent
OStevan Oct 21, 2021
fd311f2
Inline
OStevan Oct 21, 2021
312a438
Add function to enrich data
OStevan Oct 21, 2021
988adb9
Add test for enriching headers
OStevan Oct 21, 2021
d64f5f9
Take the for user agent from the API headers
OStevan Oct 21, 2021
24e9594
Better header extraction
OStevan Oct 21, 2021
a533ab7
Api break
OStevan Oct 21, 2021
03cdfad
Fix overloaded
OStevan Oct 21, 2021
258a34c
Make users of trace metadata switch to standard implementation
OStevan Oct 22, 2021
79c677d
Add a helper method
OStevan Oct 22, 2021
745c9ff
Add jersey tests
OStevan Oct 22, 2021
2d82e4c
Fix tests
OStevan Oct 22, 2021
677b043
Add helper method
OStevan Oct 22, 2021
a9bcf52
Add missing tests
OStevan Oct 22, 2021
0c133e9
Missing nullable
OStevan Oct 22, 2021
03ff6bf
Docs
OStevan Oct 22, 2021
cdc36cc
Comments
OStevan Oct 22, 2021
e4f7776
Remove unused
OStevan Oct 22, 2021
c32c600
Fix compile error
OStevan Oct 22, 2021
0ee5f24
Fix compile error
OStevan Oct 22, 2021
cddeaee
Move function
OStevan Nov 2, 2021
e485449
Remove public headers
OStevan Nov 2, 2021
6d21cc3
Remove not needed break
OStevan Nov 2, 2021
9768d77
Autorelease 6.5.0-rc1
carterkozak Nov 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/6.5.0-rc1/pr-790.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: feature
feature:
description: User agent specified inside `ForUserAgent` http header will be propagated
to requests made in the same trace.
links:
- https://github.com/palantir/tracing-java/pull/790
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface TraceHttpHeaders {
String SPAN_ID = "X-B3-SpanId";
String IS_SAMPLED = "X-B3-Sampled"; // Boolean (either “1” or “0”, can be absent)

String FOR_USER_AGENT = "For-User-Agent";

/**
* Field is no longer used by the tracing library.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public final class TraceTags {
public static final String HTTP_REQUEST_ID = "http.request_id";
/** The User-Agent as it is sent (raw format). */
public static final String HTTP_USER_AGENT = "http.useragent";
/** The User-Agent propagated across service boundaries as it is sent (raw format). */
public static final String HTTP_FOR_USER_AGENT = "http.for_useragent";
/** The version of HTTP used for the request. */
public static final String HTTP_VERSION = "http.version";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.palantir.tracing.jersey;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.palantir.tracing.Observability;
import com.palantir.tracing.TagTranslator;
Expand All @@ -35,6 +36,7 @@
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import org.glassfish.jersey.server.ExtendedUriInfo;
Expand All @@ -55,6 +57,9 @@ public final class TraceEnrichingFilter implements ContainerRequestFilter, Conta

public static final String SAMPLED_PROPERTY_NAME = "com.palantir.tracing.sampled";

@VisibleForTesting
static final String FETCH_USER_AGENT_HEADER = "Fetch-User-Agent";

@Context
@SuppressWarnings("NullAway") // instantiated using by Jersey using reflection
private ExtendedUriInfo uriInfo;
Expand All @@ -68,22 +73,33 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
// The following strings are all nullable
String traceId = requestContext.getHeaderString(TraceHttpHeaders.TRACE_ID);
String spanId = requestContext.getHeaderString(TraceHttpHeaders.SPAN_ID);
Optional<String> forUserAgent = getForUserAgent(requestContext);

// Set up thread-local span that inherits state from HTTP headers
if (Strings.isNullOrEmpty(traceId)) {
// HTTP request did not indicate a trace; initialize trace state and create a span.
Tracer.initTraceWithSpan(
getObservabilityFromHeader(requestContext),
Tracers.randomId(),
forUserAgent,
operation,
SpanType.SERVER_INCOMING);
} else if (spanId == null) {
Tracer.initTraceWithSpan(
getObservabilityFromHeader(requestContext), traceId, operation, SpanType.SERVER_INCOMING);
getObservabilityFromHeader(requestContext),
traceId,
forUserAgent,
operation,
SpanType.SERVER_INCOMING);
} else {
// caller's span is this span's parent.
Tracer.initTraceWithSpan(
getObservabilityFromHeader(requestContext), traceId, operation, spanId, SpanType.SERVER_INCOMING);
getObservabilityFromHeader(requestContext),
traceId,
forUserAgent,
operation,
spanId,
SpanType.SERVER_INCOMING);
}

// Give asynchronous downstream handlers access to the trace id
Expand Down Expand Up @@ -131,6 +147,18 @@ private static Observability getObservabilityFromHeader(ContainerRequestContext
}
}

private static Optional<String> getForUserAgent(ContainerRequestContext context) {
String forUserAgent = context.getHeaderString(TraceHttpHeaders.FOR_USER_AGENT);
if (forUserAgent != null) {
return Optional.of(forUserAgent);
}
String fetchUserAgent = context.getHeaderString(FETCH_USER_AGENT_HEADER);
if (fetchUserAgent != null) {
return Optional.of(fetchUserAgent);
}
return Optional.ofNullable(context.getHeaderString(HttpHeaders.USER_AGENT));
}

private String getPathTemplate() {
return Optional.ofNullable(uriInfo)
.map(ExtendedUriInfo::getMatchedModelResource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.palantir.tracing.InternalTracers;
import com.palantir.tracing.TraceSampler;
import com.palantir.tracing.Tracer;
import com.palantir.tracing.Tracers;
Expand All @@ -45,6 +46,7 @@
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
Expand Down Expand Up @@ -238,6 +240,34 @@ public void testFilter_setsMdcIfTraceIdHeaderIsPresent() throws Exception {
verify(request).setProperty(eq(TraceEnrichingFilter.REQUEST_ID_PROPERTY_NAME), anyString());
}

@Test
public void testFilter_setsUserAgentAsForUserAgent() throws Exception {
when(request.getHeaderString(TraceHttpHeaders.TRACE_ID)).thenReturn("traceId");
when(request.getHeaderString(HttpHeaders.USER_AGENT)).thenReturn("userAgent");
TraceEnrichingFilter.INSTANCE.filter(request);

assertThat(InternalTracers.getForUserAgent()).contains("userAgent");
}

@Test
public void testFilter_setsFetchUserAgentAsForUserAgent() throws Exception {
when(request.getHeaderString(TraceHttpHeaders.TRACE_ID)).thenReturn("traceId");
when(request.getHeaderString(TraceEnrichingFilter.FETCH_USER_AGENT_HEADER))
.thenReturn("fetchUserAgent");
TraceEnrichingFilter.INSTANCE.filter(request);

assertThat(InternalTracers.getForUserAgent()).contains("fetchUserAgent");
}

@Test
public void testFilter_propagatesProvidedForUserAgent() throws Exception {
when(request.getHeaderString(TraceHttpHeaders.TRACE_ID)).thenReturn("traceId");
when(request.getHeaderString(TraceHttpHeaders.FOR_USER_AGENT)).thenReturn("forUserAgent");
TraceEnrichingFilter.INSTANCE.filter(request);

assertThat(InternalTracers.getForUserAgent()).contains("forUserAgent");
}

@Test
public void testFilter_createsReceiveAndSendEvents() throws Exception {
target.path("/trace").request().header(TraceHttpHeaders.TRACE_ID, "").get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.palantir.tracing;

import com.palantir.logsafe.exceptions.SafeRuntimeException;
import com.palantir.tracing.api.TraceHttpHeaders;
import java.io.Closeable;
import java.io.IOException;
import java.util.function.Function;
Expand All @@ -44,14 +43,24 @@ public Response intercept(Chain chain) throws IOException {
Request request = chain.request();

try (Closeable span = createNetworkCallSpan.apply(request)) {
TraceMetadata metadata = Tracer.maybeGetTraceMetadata()
.orElseThrow(() -> new SafeRuntimeException("Trace with no spans in progress"));

return chain.proceed(request.newBuilder()
.header(TraceHttpHeaders.TRACE_ID, Tracer.getTraceId())
.header(TraceHttpHeaders.SPAN_ID, metadata.getSpanId())
.header(TraceHttpHeaders.IS_SAMPLED, Tracer.isTraceObservable() ? "1" : "0")
.build());
if (!Tracer.hasTraceId()) {
throw new SafeRuntimeException("Trace with no spans in progress");
}

Request.Builder requestBuilder = request.newBuilder();

Tracers.addTracingHeaders(requestBuilder, EnrichingFunction.INSTANCE);

return chain.proceed(requestBuilder.build());
}
}

private enum EnrichingFunction implements TracingHeadersEnrichingFunction<Request.Builder> {
INSTANCE;

@Override
public void addHeader(String headerName, String headerValue, Request.Builder state) {
state.header(headerName, headerValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
package com.palantir.tracing.okhttp3;

import com.palantir.tracing.Tracer;
import com.palantir.tracing.api.OpenSpan;
import com.palantir.tracing.Tracers;
import com.palantir.tracing.TracingHeadersEnrichingFunction;
import com.palantir.tracing.api.SpanType;
import com.palantir.tracing.api.TraceHttpHeaders;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
Expand Down Expand Up @@ -48,11 +48,9 @@ public Response intercept(Chain chain) throws IOException {
request = request.newBuilder().removeHeader(PATH_TEMPLATE_HEADER).build();
}

OpenSpan span = Tracer.startSpan(spanName, SpanType.CLIENT_OUTGOING);
Request.Builder tracedRequest = request.newBuilder()
.header(TraceHttpHeaders.TRACE_ID, Tracer.getTraceId())
.header(TraceHttpHeaders.SPAN_ID, span.getSpanId())
.header(TraceHttpHeaders.IS_SAMPLED, Tracer.isTraceObservable() ? "1" : "0");
Tracer.fastStartSpan(spanName, SpanType.CLIENT_OUTGOING);
Request.Builder tracedRequest = request.newBuilder();
Tracers.addTracingHeaders(tracedRequest, EnrichingFunction.INSTANCE);

Response response;
try {
Expand All @@ -63,4 +61,13 @@ public Response intercept(Chain chain) throws IOException {

return response;
}

private enum EnrichingFunction implements TracingHeadersEnrichingFunction<Request.Builder> {
INSTANCE;

@Override
public void addHeader(String headerName, String headerValue, Request.Builder state) {
state.header(headerName, headerValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.palantir.tracing.api.SpanType;
import com.palantir.tracing.api.TraceHttpHeaders;
import java.io.IOException;
import java.util.Optional;
import okhttp3.Interceptor;
import okhttp3.Request;
import org.junit.After;
Expand Down Expand Up @@ -104,6 +105,28 @@ public void testPopulatesNewTrace_whenParentTraceIsPresent() throws IOException
assertThat(intercepted.headers(TraceHttpHeaders.TRACE_ID)).containsOnly(traceId);
}

@Test
public void testPopulatesNewTrace_whenForUserAgentIsPresent() throws IOException {
String forUserAgent = "forUserAgent";
Tracer.initTraceWithSpan(
Observability.SAMPLE, "id", Optional.of(forUserAgent), "operation", "parent", SpanType.SERVER_INCOMING);
String traceId = Tracer.getTraceId();
try {
OkhttpTraceInterceptor.INSTANCE.intercept(chain);
} finally {
Tracer.fastCompleteSpan();
}

verify(chain).request();
verify(chain).proceed(requestCaptor.capture());
verifyNoMoreInteractions(chain);

Request intercepted = requestCaptor.getValue();
assertThat(intercepted.headers(TraceHttpHeaders.SPAN_ID)).isNotNull();
assertThat(intercepted.headers(TraceHttpHeaders.TRACE_ID)).containsOnly(traceId);
assertThat(intercepted.headers(TraceHttpHeaders.FOR_USER_AGENT)).containsOnly(forUserAgent);
}

@Test
public void testAddsIsSampledHeader_whenTraceIsObservable() throws IOException {
Tracer.initTraceWithSpan(Observability.SAMPLE, Tracers.randomId(), "op", SpanType.LOCAL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.palantir.tracing.undertow;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.exceptions.SafeIllegalStateException;
Expand All @@ -32,6 +33,7 @@
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import io.undertow.util.HeaderMap;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import java.util.Optional;

Expand All @@ -48,6 +50,11 @@ final class UndertowTracing {
private static final HttpString TRACE_ID = HttpString.tryFromString(TraceHttpHeaders.TRACE_ID);
private static final HttpString SPAN_ID = HttpString.tryFromString(TraceHttpHeaders.SPAN_ID);
private static final HttpString IS_SAMPLED = HttpString.tryFromString(TraceHttpHeaders.IS_SAMPLED);
// Tracing headers for obtaining for constructing forUserAgent.
private static final HttpString FOR_USER_AGENT = HttpString.tryFromString(TraceHttpHeaders.FOR_USER_AGENT);

@VisibleForTesting
static final HttpString FETCH_USER_AGENT = HttpString.tryFromString("Fetch-User-Agent");

// Consider moving this to TracingAttachments and making it public. For now it's well encapsulated
// here because we expect the two handler implementations to be sufficient.
Expand Down Expand Up @@ -77,7 +84,8 @@ private static DetachedSpan initializeRequestTrace(
String maybeTraceId = requestHeaders.getFirst(TRACE_ID);
boolean newTraceId = maybeTraceId == null;
String traceId = newTraceId ? Tracers.randomId() : maybeTraceId;
DetachedSpan detachedSpan = detachedSpan(operationName, newTraceId, traceId, requestHeaders);
Optional<String> forUserAgent = getForUserAgent(requestHeaders);
DetachedSpan detachedSpan = detachedSpan(operationName, newTraceId, traceId, forUserAgent, requestHeaders);
setExchangeState(exchange, detachedSpan, traceId, translator);
return detachedSpan;
}
Expand All @@ -92,7 +100,7 @@ private static void setExchangeState(
boolean isSampled = InternalTracers.isSampled(detachedSpan);
exchange.putAttachment(TracingAttachments.IS_SAMPLED, isSampled);
Optional<String> requestId = InternalTracers.getRequestId(detachedSpan);
if (!requestId.isPresent()) {
if (requestId.isEmpty()) {
throw new SafeIllegalStateException("No requestId is set", SafeArg.of("span", detachedSpan));
}
exchange.putAttachment(TracingAttachments.REQUEST_ID, requestId.get());
Expand All @@ -102,10 +110,15 @@ private static void setExchangeState(
}

private static DetachedSpan detachedSpan(
String operationName, boolean newTrace, String traceId, HeaderMap requestHeaders) {
String operationName,
boolean newTrace,
String traceId,
Optional<String> forUserAgent,
HeaderMap requestHeaders) {
return DetachedSpan.start(
getObservabilityFromHeader(requestHeaders),
traceId,
forUserAgent,
newTrace ? Optional.empty() : Optional.ofNullable(requestHeaders.getFirst(SPAN_ID)),
operationName,
SpanType.SERVER_INCOMING);
Expand Down Expand Up @@ -148,5 +161,17 @@ private static Observability getObservabilityFromHeader(HeaderMap headers) {
}
}

private static Optional<String> getForUserAgent(HeaderMap requestHeaders) {
String forUserAgent = requestHeaders.getFirst(FOR_USER_AGENT);
if (forUserAgent != null) {
return Optional.of(forUserAgent);
}
String fetchUserAgent = requestHeaders.getFirst(FETCH_USER_AGENT);
if (fetchUserAgent != null) {
return Optional.of(fetchUserAgent);
}
return Optional.ofNullable(requestHeaders.getFirst(Headers.USER_AGENT));
}

private UndertowTracing() {}
}
Loading