Skip to content

Commit

Permalink
Reads and writes OpenCensus binary formatted trace header in gRPC
Browse files Browse the repository at this point in the history
This prefers the OpenCensus binary formatted trace header, falling back
to Brave defaults if absent, upon server requests. On the client side,
both are written.

This allows interop with gRPC services that don't use B3 (for example,
the default census module). This also acknowledges that the OpenCensus
binary format is fairly contained within gRPC, so doesn't need to become
a top-level propagation format, for example used with non-gRPC services
as yet.

Redundantly encoding this format has some cost to it, notable 14
character header name "grpc-trace-bin" and the 38 character base64
encoded binary context value. This also writes "grpc-tags-bin".

Note that this only allows black-box interop with Census. It does not
(yet) interop with census in the same process as Brave. However, this
should be a helpful step to those especially currently using Brave, but
trying out Census for non-java services.
  • Loading branch information
Adrian Cole committed Jun 13, 2018
1 parent a56c6b2 commit ea69c49
Show file tree
Hide file tree
Showing 16 changed files with 1,073 additions and 77 deletions.
22 changes: 22 additions & 0 deletions instrumentation/grpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ overrideSpanName = new GrpcClientParser() {
};
```

## gRPC Propagation Format (Census interop)

gRPC defines a [binary encoded propagation format](https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md) which is implemented
by [OpenCensus](https://opencensus.io/) instrumentation. When this is
the case, incoming requests will have two metadata keys "grpc-trace-bin"
and "grpc-tags-bin".

When enabled, this component can extract trace contexts from these
metadata and also write the same keys on outgoing calls. This allows
transparent interop when both census and brave report data to the same
tracing system.

To enable this feature, set `grpcPropagationFormatEnabled` which is off
by default:
```java
grpcTracing = GrpcTracing.newBuilder(tracing)
.grpcPropagationFormatEnabled(true).build();
```

Warning: the format of both "grpc-trace-bin" and "grpc-tags-bin" are
version 0. As such, consider this feature experimental.

## Development

If you are working on this module, then you need to run `mvn install` to first compile the protos. Once the protos are compiled, then can be found in the directories:
Expand Down
25 changes: 18 additions & 7 deletions instrumentation/grpc/pom.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-parent</artifactId>
Expand All @@ -12,6 +14,7 @@
<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
<protobuf.version>3.5.1</protobuf.version>
<opencensus.version>0.14.0</opencensus.version>
<os-maven-plugin.version>1.5.0.Final</os-maven-plugin.version>
<protobuf-maven-plugin.version>0.5.1</protobuf-maven-plugin.version>
</properties>
Expand All @@ -23,11 +26,6 @@
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<scope>provided</scope>
</dependency>
<!-- generated code includes Generated annotations! -->
<dependency>
<groupId>javax.annotation</groupId>
Expand All @@ -44,6 +42,18 @@
<artifactId>brave-context-log4j2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opencensus</groupId>
<artifactId>opencensus-impl-lite</artifactId>
<version>${opencensus.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opencensus</groupId>
<artifactId>opencensus-testing</artifactId>
<version>${opencensus.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -94,7 +104,8 @@
<configuration>
<extraArtifacts>
<extraArtifact>io.grpc:grpc-all:1.2.0:jar</extraArtifact>
<extraArtifact>com.google.protobuf:protoc:3.2.0:exe:${os.detected.classifier}</extraArtifact>
<extraArtifact>com.google.protobuf:protoc:3.2.0:exe:${os.detected.classifier}
</extraArtifact>
</extraArtifacts>
</configuration>
</plugin>
Expand Down
10 changes: 10 additions & 0 deletions instrumentation/grpc/src/it/grpc12/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@
</extension>
</extensions>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>@maven-compiler-plugin.version@</version>
<configuration>
<!-- Census doesn't exist in grpc 1.2 -->
<excludes>
<exclude>**/ITCensusInterop*.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>@maven-failsafe-plugin.version@</version>
Expand Down
191 changes: 191 additions & 0 deletions instrumentation/grpc/src/main/java/brave/grpc/GrpcPropagation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package brave.grpc;

import brave.internal.MapPropagationFields;
import brave.internal.Nullable;
import brave.internal.PropagationFieldsFactory;
import brave.propagation.Propagation;
import brave.propagation.TraceContext;
import brave.propagation.TraceContext.Extractor;
import brave.propagation.TraceContext.Injector;
import brave.propagation.TraceContextOrSamplingFlags;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import java.util.List;
import java.util.Map;

/** see {@link GrpcTracing.Builder#grpcPropagationFormatEnabled} for documentation. */
final class GrpcPropagation<K> implements Propagation<K> {

/**
* This creates a compatible metadata key based on Census, except this extracts a brave trace
* context as opposed to a census span context
*/
static final Metadata.Key<TraceContext> GRPC_TRACE_BIN =
Metadata.Key.of("grpc-trace-bin", new TraceContextBinaryMarshaller());

/** This stashes the tag context in "extra" so it isn't lost */
static final Metadata.Key<Map<String, String>> GRPC_TAGS_BIN =
Metadata.Key.of("grpc-tags-bin", new TagContextBinaryMarshaller());

/** The census tag key corresponding to the {@link MethodDescriptor#fullMethodName}. */
static final String RPC_METHOD = "method";

static Propagation.Factory newFactory(Propagation.Factory delegate) {
if (delegate == null) throw new NullPointerException("delegate == null");
return new Factory(delegate);
}

static final class Factory extends Propagation.Factory {
final Propagation.Factory delegate;
final TagsFactory tagsFactory = new TagsFactory();

Factory(Propagation.Factory delegate) {
this.delegate = delegate;
}

@Override
public boolean supportsJoin() {
return false;
}

@Override
public boolean requires128BitTraceId() {
return true;
}

@Override
public final <K> Propagation<K> create(KeyFactory<K> keyFactory) {
return new GrpcPropagation<>(this, keyFactory);
}

@Override
public TraceContext decorate(TraceContext context) {
TraceContext result = delegate.decorate(context);
return tagsFactory.decorate(result);
}
}

final Propagation<K> delegate;
final TagsFactory extraFactory;

GrpcPropagation(Factory factory, KeyFactory<K> keyFactory) {
this.delegate = factory.delegate.create(keyFactory);
this.extraFactory = factory.tagsFactory;
}

@Override
public List<K> keys() {
return delegate.keys();
}

@Override
public <C> Injector<C> injector(Setter<C, K> setter) {
return new GrpcInjector<>(this, setter);
}

@Override
public <C> Extractor<C> extractor(Getter<C, K> getter) {
return new GrpcExtractor<>(this, getter);
}

static final class GrpcInjector<C, K> implements Injector<C> {
final Injector<C> delegate;
final Propagation.Setter<C, K> setter;

GrpcInjector(GrpcPropagation<K> propagation, Setter<C, K> setter) {
this.delegate = propagation.delegate.injector(setter);
this.setter = setter;
}

@Override
public void inject(TraceContext traceContext, C carrier) {
if (carrier instanceof Metadata) {
((Metadata) carrier).put(GRPC_TRACE_BIN, traceContext);
Tags tags = findTags(traceContext);
if (tags != null) ((Metadata) carrier).put(GRPC_TAGS_BIN, tags.toMap());
}
delegate.inject(traceContext, carrier);
}
}

@Nullable
static Tags findTags(TraceContext traceContext) {
List<Object> extra = traceContext.extra();
for (int i = 0, length = extra.size(); i < length; i++) {
Object next = extra.get(i);
if (next instanceof GrpcPropagation.Tags) {
return (Tags) next;
}
}
return null;
}

static final class GrpcExtractor<C, K> implements Extractor<C> {
final GrpcPropagation<K> propagation;
final Extractor<C> delegate;
final Propagation.Getter<C, K> getter;

GrpcExtractor(GrpcPropagation<K> propagation, Getter<C, K> getter) {
this.propagation = propagation;
this.delegate = propagation.delegate.extractor(getter);
this.getter = getter;
}

@Override
public TraceContextOrSamplingFlags extract(C carrier) {
Tags tags = null;
if (carrier instanceof Metadata) {
TraceContext extractedTrace = ((Metadata) carrier).get(GRPC_TRACE_BIN);
Map<String, String> extractedTags = ((Metadata) carrier).get(GRPC_TAGS_BIN);
if (extractedTags != null) {
tags = new Tags(extractedTags, extractedTags.remove(RPC_METHOD));
}
if (extractedTrace != null) {
if (tags == null) return TraceContextOrSamplingFlags.create(extractedTrace);
return TraceContextOrSamplingFlags.newBuilder()
.addExtra(tags)
.context(extractedTrace)
.build();
}
}
TraceContextOrSamplingFlags result = delegate.extract(carrier);
if (tags == null) return result;
return result.toBuilder().addExtra(tags).build();
}
}

static final class TagsFactory extends PropagationFieldsFactory<Tags> {
@Override
protected Class type() {
return Tags.class;
}

@Override
protected Tags create() {
return new Tags();
}

@Override
protected Tags create(Tags parent) {
return new Tags(parent);
}
}

static final class Tags extends MapPropagationFields {
final String parentMethod;

Tags() {
parentMethod = null;
}

Tags(Tags parent) {
super(parent);
parentMethod = null;
}

Tags(Map<String, String> extracted, String parentMethod) {
super(extracted);
this.parentMethod = parentMethod;
}
}
}
Loading

0 comments on commit ea69c49

Please # to comment.