Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally include/exclude grpc headers by name #58

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.contrib.grpc.TracingServerInterceptor.Builder;
import io.opentracing.contrib.grpc.TracingServerInterceptor.ServerRequestAttribute;
import io.opentracing.log.Fields;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
Expand Down Expand Up @@ -60,6 +62,8 @@ public class TracingClientInterceptor implements ClientInterceptor {
private final ActiveSpanContextSource activeSpanContextSource;
private final ImmutableList<ClientSpanDecorator> clientSpanDecorators;
private final ImmutableList<ClientCloseDecorator> clientCloseDecorators;
private final Set<Metadata.Key<?>> excludedHeaders;
private final Set<Metadata.Key<?>> includedHeaders;

private TracingClientInterceptor(Builder builder) {
this.tracer = builder.tracer;
Expand All @@ -71,6 +75,11 @@ private TracingClientInterceptor(Builder builder) {
this.activeSpanContextSource = builder.activeSpanContextSource;
this.clientSpanDecorators = ImmutableList.copyOf(builder.clientSpanDecorators.values());
this.clientCloseDecorators = ImmutableList.copyOf(builder.clientCloseDecorators.values());
if(builder.tracedAttributes.contains(ClientRequestAttribute.HEADERS) && builder.excludedHeaders.size() > 0 && builder.includedHeaders.size() > 0) {
throw new IllegalArgumentException("Only one of excludedHeaders or includedHeaders can be defined at the same time.");
}
this.excludedHeaders = builder.excludedHeaders;
this.includedHeaders = builder.includedHeaders;
}

/**
Expand Down Expand Up @@ -150,7 +159,7 @@ public void start(Listener<RespT> responseListener, final Metadata headers) {
.build());
}
if (tracedAttributes.contains(ClientRequestAttribute.HEADERS)) {
GrpcTags.GRPC_HEADERS.set(span, headers);
GrpcTags.GRPC_HEADERS.set(span, massageHeaders(headers));
}

tracer.inject(
Expand Down Expand Up @@ -321,6 +330,22 @@ private Span createSpanFromParent(SpanContext parentSpanContext, String operatio
.withTag(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME)
.start();
}

private Metadata massageHeaders(Metadata headers) {
if(this.excludedHeaders.size() > 0) {
Metadata massagedHeaders = new Metadata();
massagedHeaders.merge(headers);
for(Metadata.Key<?> excludedHeader : this.excludedHeaders) {
massagedHeaders.removeAll(excludedHeader);
}
return massagedHeaders;
} else if(this.includedHeaders.size() > 0) {
Metadata massagedHeaders = new Metadata();
massagedHeaders.merge(headers, this.includedHeaders);
return massagedHeaders;
}
return headers;
}

/**
* Builds the configuration of a TracingClientInterceptor.
Expand All @@ -336,6 +361,8 @@ public static class Builder {
private ActiveSpanContextSource activeSpanContextSource;
private Map<Class<?>, ClientSpanDecorator> clientSpanDecorators;
private Map<Class<?>, ClientCloseDecorator> clientCloseDecorators;
private Set<Metadata.Key<?>> excludedHeaders;
private Set<Metadata.Key<?>> includedHeaders;

/**
* Creates a Builder with GlobalTracer if present else NoopTracer.
Expand All @@ -350,6 +377,8 @@ private Builder() {
this.activeSpanContextSource = ActiveSpanContextSource.NONE;
this.clientSpanDecorators = new HashMap<>();
this.clientCloseDecorators = new HashMap<>();
this.excludedHeaders = new HashSet<>();
this.includedHeaders = new HashSet<>();
}

/**
Expand Down Expand Up @@ -394,6 +423,35 @@ public Builder withTracedAttributes(ClientRequestAttribute... tracedAttributes)
this.tracedAttributes = new HashSet<>(Arrays.asList(tracedAttributes));
return this;
}

/**
* Provide keys for headers that should be excluded from the trace. Only applicable
* when {@link #withTracedAttributes(ClientRequestAttribute...)} contains {@link ClientRequestAttribute#HEADERS}.
*
* Mutually exclusive with {@link #withIncludedHeaders(io.grpc.Metadata.Key...)}.
*
* @param headerKeys
* @return this Builder configured to exclude the header keys
*/
public Builder withExcludedHeaders(Metadata.Key<?>... headerKeys) {
this.excludedHeaders = new HashSet<>(Arrays.asList(headerKeys));
return this;
}

/**
* Provide keys for headers that should be included in the trace. Headers with any other
* key will be excluded. Only applicable when {@link #withTracedAttributes(ClientRequestAttribute...)}
* contains {@link ClientRequestAttribute#HEADERS}.
*
* Mutually exclusive with {@link #withExcludedHeaders(io.grpc.Metadata.Key...)}.
*
* @param headerKeys
* @return this Builder configured to exclude the header keys
*/
public Builder withIncludedHeaders(Metadata.Key<?>... headerKeys) {
this.includedHeaders = new HashSet<>(Arrays.asList(headerKeys));
return this;
}

/**
* Logs all request life-cycle events to client spans.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class TracingServerInterceptor implements ServerInterceptor {
private final Set<ServerRequestAttribute> tracedAttributes;
private final ImmutableList<ServerSpanDecorator> serverSpanDecorators;
private final ImmutableList<ServerCloseDecorator> serverCloseDecorators;
private final Set<Metadata.Key<?>> excludedHeaders;
private final Set<Metadata.Key<?>> includedHeaders;

private TracingServerInterceptor(Builder builder) {
this.tracer = builder.tracer;
Expand All @@ -65,6 +67,11 @@ private TracingServerInterceptor(Builder builder) {
this.tracedAttributes = builder.tracedAttributes;
this.serverSpanDecorators = ImmutableList.copyOf(builder.serverSpanDecorators.values());
this.serverCloseDecorators = ImmutableList.copyOf(builder.serverCloseDecorators.values());
if(builder.tracedAttributes.contains(ServerRequestAttribute.HEADERS) && builder.excludedHeaders.size() > 0 && builder.includedHeaders.size() > 0) {
throw new IllegalArgumentException("Only one of excludedHeaders or includedHeaders can be defined at the same time.");
}
this.excludedHeaders = builder.excludedHeaders;
this.includedHeaders = builder.includedHeaders;
}

/**
Expand Down Expand Up @@ -131,7 +138,7 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
GrpcTags.GRPC_CALL_ATTRIBUTES.set(span, call.getAttributes());
break;
case HEADERS:
GrpcTags.GRPC_HEADERS.set(span, headers);
GrpcTags.GRPC_HEADERS.set(span, massageHeaders(headers));
break;
case PEER_ADDRESS:
GrpcTags.PEER_ADDRESS.set(span, call.getAttributes());
Expand Down Expand Up @@ -303,6 +310,22 @@ Span getSpanFromHeaders(Map<String, String> headers, String operationName) {
}
return span;
}

private Metadata massageHeaders(Metadata headers) {
if(this.excludedHeaders.size() > 0) {
Metadata massagedHeaders = new Metadata();
massagedHeaders.merge(headers);
for(Metadata.Key<?> excludedHeader : this.excludedHeaders) {
massagedHeaders.removeAll(excludedHeader);
}
return massagedHeaders;
} else if(this.includedHeaders.size() > 0) {
Metadata massagedHeaders = new Metadata();
massagedHeaders.merge(headers, this.includedHeaders);
return massagedHeaders;
}
return headers;
}

/**
* Builds the configuration of a TracingServerInterceptor.
Expand All @@ -316,6 +339,8 @@ public static class Builder {
private Set<ServerRequestAttribute> tracedAttributes;
private Map<Class<?>, ServerSpanDecorator> serverSpanDecorators;
private Map<Class<?>, ServerCloseDecorator> serverCloseDecorators;
private Set<Metadata.Key<?>> excludedHeaders;
private Set<Metadata.Key<?>> includedHeaders;

/**
* Creates a Builder with GlobalTracer if present else NoopTracer.
Expand All @@ -328,6 +353,8 @@ private Builder() {
this.tracedAttributes = new HashSet<>();
this.serverSpanDecorators = new HashMap<>();
this.serverCloseDecorators = new HashMap<>();
this.excludedHeaders = new HashSet<>();
this.includedHeaders = new HashSet<>();
}

/**
Expand Down Expand Up @@ -362,6 +389,35 @@ public Builder withTracedAttributes(ServerRequestAttribute... attributes) {
this.tracedAttributes = new HashSet<>(Arrays.asList(attributes));
return this;
}

/**
* Provide keys for headers that should be excluded from the trace. Only applicable
* when {@link #withTracedAttributes(ServerRequestAttribute...)} contains {@link ServerRequestAttribute#HEADERS}.
*
* Mutually exclusive with {@link #withIncludedHeaders(io.grpc.Metadata.Key...)}.
*
* @param headerKeys
* @return this Builder configured to exclude the header keys
*/
public Builder withExcludedHeaders(Metadata.Key<?>... headerKeys) {
this.excludedHeaders = new HashSet<>(Arrays.asList(headerKeys));
return this;
}

/**
* Provide keys for headers that should be included in the trace. Headers with any other
* key will be excluded. Only applicable when {@link #withTracedAttributes(ServerRequestAttribute...)}
* contains {@link ServerRequestAttribute#HEADERS}.
*
* Mutually exclusive with {@link #withExcludedHeaders(io.grpc.Metadata.Key...)}.
*
* @param headerKeys
* @return this Builder configured to exclude the header keys
*/
public Builder withIncludedHeaders(Metadata.Key<?>... headerKeys) {
this.includedHeaders = new HashSet<>(Arrays.asList(headerKeys));
return this;
}

/**
* Logs streaming events to server spans.
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/io/opentracing/contrib/grpc/TracedClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
import io.grpc.ClientInterceptor;
import io.grpc.ClientInterceptors;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import io.opentracing.contrib.grpc.gen.GreeterGrpc;
import io.opentracing.contrib.grpc.gen.GreeterGrpc.GreeterBlockingStub;
import io.opentracing.contrib.grpc.gen.HelloReply;
import io.opentracing.contrib.grpc.gen.HelloRequest;
import java.util.Map;
import java.util.concurrent.TimeUnit;

class TracedClient {
Expand Down Expand Up @@ -48,4 +52,17 @@ HelloReply greet() {
return null;
}
}

HelloReply greetWithHeaders(Map<String, String> headers) {
try {
Metadata metadata = new Metadata();
for(Map.Entry<String, String> entry : headers.entrySet()) {
metadata.put(Metadata.Key.of(entry.getKey(), Metadata.ASCII_STRING_MARSHALLER), entry.getValue());
}
GreeterBlockingStub stub = MetadataUtils.attachHeaders(blockingStub, metadata);
return stub.sayHello(HelloRequest.newBuilder().setName("world").build());
} catch(Exception ignored) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracerTestUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -240,7 +241,7 @@ public void testTracedClientWithTracedAttributes() {
.withTracer(clientTracer)
.withTracedAttributes(TracingClientInterceptor.ClientRequestAttribute.values())
.build();
TracedClient client = new TracedClient(grpcServer.getChannel(), 50, "gzip", tracingInterceptor);
TracedClient client = new TracedClient(grpcServer.getChannel(), 5000, "gzip", tracingInterceptor);

assertEquals("call should complete successfully", "Hello world", client.greet().getMessage());
await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1));
Expand All @@ -261,6 +262,80 @@ public void testTracedClientWithTracedAttributes() {
.containsAll(CLIENT_ATTRIBUTE_TAGS);
assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext());
}

@Test
public void testTracedClientWithExcludedHeader() {
TracingClientInterceptor tracingInterceptor =
TracingClientInterceptor.newBuilder()
.withTracer(clientTracer)
.withTracedAttributes(TracingClientInterceptor.ClientRequestAttribute.HEADERS)
.withExcludedHeaders(Metadata.Key.of("header2", Metadata.ASCII_STRING_MARSHALLER))
.build();
TracedClient client = new TracedClient(grpcServer.getChannel(), 5000, "gzip", tracingInterceptor);

Map<String, String> headers = new HashMap<>();
headers.put("header1", "value1");
headers.put("header2", "value2");

assertEquals("call should complete successfully", "Hello world", client.greetWithHeaders(headers).getMessage());
await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1));
assertEquals(
"one span should have been created and finished for one client request",
clientTracer.finishedSpans().size(),
1);

MockSpan span = clientTracer.finishedSpans().get(0);
assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello");
assertEquals("span should have no parents", span.parentId(), 0);
assertEquals("span should have no logs", span.logEntries().size(), 0);
Assertions.assertThat(span.tags())
.as("span should have base server tags")
.containsKey(GrpcTags.GRPC_HEADERS.getKey());
String headerString = (String) span.tags().get(GrpcTags.GRPC_HEADERS.getKey());
Assertions.assertThat(headerString)
.as("headers should have header1")
.contains("header1");
Assertions.assertThat(headerString)
.as("headers should NOT have header2")
.doesNotContain("header2");
}

@Test
public void testTracedClientWithIncludedHeader() {
TracingClientInterceptor tracingInterceptor =
TracingClientInterceptor.newBuilder()
.withTracer(clientTracer)
.withTracedAttributes(TracingClientInterceptor.ClientRequestAttribute.HEADERS)
.withIncludedHeaders(Metadata.Key.of("header2", Metadata.ASCII_STRING_MARSHALLER))
.build();
TracedClient client = new TracedClient(grpcServer.getChannel(), 5000, "gzip", tracingInterceptor);

Map<String, String> headers = new HashMap<>();
headers.put("header1", "value1");
headers.put("header2", "value2");

assertEquals("call should complete successfully", "Hello world", client.greetWithHeaders(headers).getMessage());
await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1));
assertEquals(
"one span should have been created and finished for one client request",
clientTracer.finishedSpans().size(),
1);

MockSpan span = clientTracer.finishedSpans().get(0);
assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello");
assertEquals("span should have no parents", span.parentId(), 0);
assertEquals("span should have no logs", span.logEntries().size(), 0);
Assertions.assertThat(span.tags())
.as("span should have base server tags")
.containsKey(GrpcTags.GRPC_HEADERS.getKey());
String headerString = (String) span.tags().get(GrpcTags.GRPC_HEADERS.getKey());
Assertions.assertThat(headerString)
.as("headers should NOT have header1")
.doesNotContain("header1");
Assertions.assertThat(headerString)
.as("headers should have header2")
.contains("header2");
}

@Test
public void testTracedClientwithActiveSpanSource() {
Expand Down
Loading