forked from grpc/grpc-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
otel tracing: add binary format, grpcTraceBinContextPropagator
- Loading branch information
1 parent
1ec5fab
commit 13f7755
Showing
8 changed files
with
867 additions
and
0 deletions.
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
opentelemetry/src/main/java/io/grpc/opentelemetry/BinaryFormat.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* Copyright 2024 The gRPC Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.grpc.opentelemetry; | ||
|
||
|
||
import io.opentelemetry.api.trace.SpanContext; | ||
|
||
/** | ||
* This is a helper class for SpanContext propagation on the wire using binary encoding. | ||
*/ | ||
abstract class BinaryFormat { | ||
public byte[] toByteArray(SpanContext spanContext) { | ||
throw new UnsupportedOperationException("not implemented"); | ||
} | ||
|
||
public SpanContext fromByteArray(byte[] bytes) { | ||
throw new UnsupportedOperationException("not implemented"); | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
opentelemetry/src/main/java/io/grpc/opentelemetry/BinaryFormatImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/* | ||
* Copyright 2024 The gRPC Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.grpc.opentelemetry; | ||
|
||
|
||
import static com.google.common.base.Preconditions.checkNotNull; | ||
|
||
import io.opentelemetry.api.trace.SpanContext; | ||
import io.opentelemetry.api.trace.SpanId; | ||
import io.opentelemetry.api.trace.TraceFlags; | ||
import io.opentelemetry.api.trace.TraceId; | ||
import io.opentelemetry.api.trace.TraceState; | ||
import java.util.Arrays; | ||
|
||
/** | ||
* Binary encoded {@link SpanContext} for context propagation. This is adapted from OpenCensus | ||
* binary format. | ||
* | ||
* <p>BinaryFormat format: | ||
* | ||
* <ul> | ||
* <li>Binary value: <version_id><version_format> | ||
* <li>version_id: 1-byte representing the version id. | ||
* <li>For version_id = 0: | ||
* <ul> | ||
* <li>version_format: <field><field> | ||
* <li>field_format: <field_id><field_format> | ||
* <li>Fields: | ||
* <ul> | ||
* <li>TraceId: (field_id = 0, len = 16, default = "0000000000000000") - | ||
* 16-byte array representing the trace_id. | ||
* <li>SpanId: (field_id = 1, len = 8, default = "00000000") - 8-byte array | ||
* representing the span_id. | ||
* <li>TraceFlags: (field_id = 2, len = 1, default = "0") - 1-byte array | ||
* representing the trace_flags. | ||
* </ul> | ||
* <li>Fields MUST be encoded using the field id order (smaller to higher). | ||
* <li>Valid value example: | ||
* <ul> | ||
* <li>{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, | ||
* 98, 99, 100, 101, 102, 103, 104, 2, 1} | ||
* <li>version_id = 0; | ||
* <li>trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79} | ||
* <li>span_id = {97, 98, 99, 100, 101, 102, 103, 104}; | ||
* <li>trace_flags = {1}; | ||
* </ul> | ||
* </ul> | ||
* </ul> | ||
*/ | ||
final class BinaryFormatImpl extends BinaryFormat { | ||
private static final byte VERSION_ID = 0; | ||
private static final int VERSION_ID_OFFSET = 0; | ||
private static final byte ID_SIZE = 1; | ||
private static final byte TRACE_ID_FIELD_ID = 0; | ||
|
||
private static final int TRACE_ID_FIELD_ID_OFFSET = VERSION_ID_OFFSET + ID_SIZE; | ||
private static final int TRACE_ID_OFFSET = TRACE_ID_FIELD_ID_OFFSET + ID_SIZE; | ||
private static final int TRACE_ID_SIZE = TraceId.getLength() / 2; | ||
|
||
private static final byte SPAN_ID_FIELD_ID = 1; | ||
private static final int SPAN_ID_FIELD_ID_OFFSET = TRACE_ID_OFFSET + TRACE_ID_SIZE; | ||
private static final int SPAN_ID_OFFSET = SPAN_ID_FIELD_ID_OFFSET + ID_SIZE; | ||
private static final int SPAN_ID_SIZE = SpanId.getLength() / 2; | ||
|
||
private static final byte TRACE_FLAG_FIELD_ID = 2; | ||
private static final int TRACE_FLAG_FIELD_ID_OFFSET = SPAN_ID_OFFSET + SPAN_ID_SIZE; | ||
private static final int TRACE_FLAG_OFFSET = TRACE_FLAG_FIELD_ID_OFFSET + ID_SIZE; | ||
private static final int REQUIRED_FORMAT_LENGTH = 3 * ID_SIZE + TRACE_ID_SIZE + SPAN_ID_SIZE; | ||
private static final int TRACE_FLAG_SIZE = TraceFlags.getLength() / 2; | ||
private static final int ALL_FORMAT_LENGTH = REQUIRED_FORMAT_LENGTH + ID_SIZE + TRACE_FLAG_SIZE; | ||
|
||
private static final BinaryFormatImpl INSTANCE = new BinaryFormatImpl(); | ||
|
||
public static BinaryFormatImpl getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
@Override | ||
public byte[] toByteArray(SpanContext spanContext) { | ||
checkNotNull(spanContext, "spanContext"); | ||
byte[] bytes = new byte[ALL_FORMAT_LENGTH]; | ||
bytes[VERSION_ID_OFFSET] = VERSION_ID; | ||
bytes[TRACE_ID_FIELD_ID_OFFSET] = TRACE_ID_FIELD_ID; | ||
System.arraycopy(spanContext.getTraceIdBytes(), 0, bytes, TRACE_ID_OFFSET, TRACE_ID_SIZE); | ||
bytes[SPAN_ID_FIELD_ID_OFFSET] = SPAN_ID_FIELD_ID; | ||
System.arraycopy(spanContext.getSpanIdBytes(), 0, bytes, SPAN_ID_OFFSET, SPAN_ID_SIZE); | ||
bytes[TRACE_FLAG_FIELD_ID_OFFSET] = TRACE_FLAG_FIELD_ID; | ||
bytes[TRACE_FLAG_OFFSET] = spanContext.getTraceFlags().asByte(); | ||
return bytes; | ||
} | ||
|
||
@Override | ||
public SpanContext fromByteArray(byte[] bytes) { | ||
checkNotNull(bytes, "bytes"); | ||
if (bytes.length == 0 || bytes[0] != VERSION_ID) { | ||
throw new IllegalArgumentException("Unsupported version."); | ||
} | ||
if (bytes.length < REQUIRED_FORMAT_LENGTH) { | ||
throw new IllegalArgumentException("Invalid input: truncated"); | ||
} | ||
String traceId; | ||
String spanId; | ||
TraceFlags traceFlags = TraceFlags.getDefault(); | ||
int pos = 1; | ||
if (bytes[pos] == TRACE_ID_FIELD_ID) { | ||
traceId = TraceId.fromBytes( | ||
Arrays.copyOfRange(bytes, pos + ID_SIZE, pos + ID_SIZE + TRACE_ID_SIZE)); | ||
pos += ID_SIZE + TRACE_ID_SIZE; | ||
} else { | ||
throw new IllegalArgumentException("Invalid input: expected trace ID at offset " + pos); | ||
} | ||
if (bytes[pos] == SPAN_ID_FIELD_ID) { | ||
spanId = SpanId.fromBytes( | ||
Arrays.copyOfRange(bytes, pos + ID_SIZE, pos + ID_SIZE + SPAN_ID_SIZE)); | ||
pos += ID_SIZE + SPAN_ID_SIZE; | ||
} else { | ||
throw new IllegalArgumentException("Invalid input: expected span ID at offset " + pos); | ||
} | ||
if (bytes.length > pos && bytes[pos] == TRACE_FLAG_FIELD_ID) { | ||
if (bytes.length < ALL_FORMAT_LENGTH) { | ||
throw new IllegalArgumentException("Invalid input: truncated"); | ||
} | ||
traceFlags = TraceFlags.fromByte(bytes[pos + ID_SIZE]); | ||
} | ||
return SpanContext.create(traceId, spanId, traceFlags, TraceState.getDefault()); | ||
} | ||
} |
135 changes: 135 additions & 0 deletions
135
opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* Copyright 2024 The gRPC Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.grpc.opentelemetry; | ||
|
||
|
||
import static com.google.common.base.Preconditions.checkNotNull; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.io.BaseEncoding; | ||
import io.grpc.ExperimentalApi; | ||
import io.grpc.Metadata; | ||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.api.trace.SpanContext; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.propagation.TextMapGetter; | ||
import io.opentelemetry.context.propagation.TextMapPropagator; | ||
import io.opentelemetry.context.propagation.TextMapSetter; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import javax.annotation.Nullable; | ||
|
||
/** | ||
* A {@link TextMapPropagator} for transmitting "grpc-trace-bin" span context. | ||
* | ||
* <p>This propagator can transmit the "grpc-trace-bin" context in either binary or Base64-encoded | ||
* text format, depending on the capabilities of the provided {@link TextMapGetter} and | ||
* {@link TextMapSetter}. | ||
* | ||
* <p>If the {@code TextMapGetter} and {@code TextMapSetter} only support text format, Base64 | ||
* encoding and decoding will be used when communicating with the carrier API. But gRPC uses | ||
* it with gRPC's metadata-based getter/setter, and the propagator can directly transmit the binary | ||
* header, avoiding the need for Base64 encoding. | ||
*/ | ||
|
||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11400") | ||
public final class GrpcTraceBinContextPropagator implements TextMapPropagator { | ||
private static final Logger log = Logger.getLogger(GrpcTraceBinContextPropagator.class.getName()); | ||
public static final String GRPC_TRACE_BIN_HEADER = "grpc-trace-bin"; | ||
private final BinaryFormat binaryFormat; | ||
private static final GrpcTraceBinContextPropagator INSTANCE = | ||
new GrpcTraceBinContextPropagator(BinaryFormatImpl.getInstance()); | ||
|
||
public static GrpcTraceBinContextPropagator defaultInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
@VisibleForTesting | ||
GrpcTraceBinContextPropagator(BinaryFormat binaryFormat) { | ||
this.binaryFormat = checkNotNull(binaryFormat, "binaryFormat"); | ||
} | ||
|
||
@Override | ||
public Collection<String> fields() { | ||
return Collections.singleton(GRPC_TRACE_BIN_HEADER); | ||
} | ||
|
||
@Override | ||
public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter) { | ||
if (context == null || setter == null) { | ||
return; | ||
} | ||
SpanContext spanContext = Span.fromContext(context).getSpanContext(); | ||
if (!spanContext.isValid()) { | ||
return; | ||
} | ||
try { | ||
byte[] b = binaryFormat.toByteArray(spanContext); | ||
if (setter instanceof MetadataSetter) { | ||
((MetadataSetter) setter).set((Metadata) carrier, GRPC_TRACE_BIN_HEADER, b); | ||
} else { | ||
setter.set(carrier, GRPC_TRACE_BIN_HEADER, BaseEncoding.base64().encode(b)); | ||
} | ||
} catch (Exception e) { | ||
log.log(Level.FINE, "Set grpc-trace-bin spanContext failed", e); | ||
} | ||
} | ||
|
||
@Override | ||
public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter) { | ||
if (context == null) { | ||
return Context.root(); | ||
} | ||
if (getter == null) { | ||
return context; | ||
} | ||
byte[] b; | ||
if (getter instanceof MetadataGetter) { | ||
b = ((MetadataGetter) getter).getBinary((Metadata) carrier, GRPC_TRACE_BIN_HEADER); | ||
if (b == null) { | ||
log.log(Level.FINE, "No grpc-trace-bin present in carrier"); | ||
return context; | ||
} | ||
} else { | ||
String value = getter.get(carrier, GRPC_TRACE_BIN_HEADER); | ||
if (value == null) { | ||
log.log(Level.FINE, "No grpc-trace-bin present in carrier"); | ||
return context; | ||
} | ||
try { | ||
b = BaseEncoding.base64().decode(value); | ||
} catch (Exception e) { | ||
log.log(Level.FINE, "Base64-decode spanContext bytes failed"); | ||
return context; | ||
} | ||
} | ||
|
||
SpanContext spanContext; | ||
try { | ||
spanContext = binaryFormat.fromByteArray(b); | ||
} catch (Exception e) { | ||
log.log(Level.FINE, "Failed to parse tracing header", e); | ||
return context; | ||
} | ||
if (!spanContext.isValid()) { | ||
return context; | ||
} | ||
return context.with(Span.wrap(spanContext)); | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataGetter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Copyright 2024 The gRPC Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.grpc.opentelemetry; | ||
|
||
|
||
import io.grpc.Metadata; | ||
import io.opentelemetry.context.propagation.TextMapGetter; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import javax.annotation.Nullable; | ||
|
||
/** | ||
* A TextMapGetter that reads value from gRPC {@link Metadata}. Supports both text and binary | ||
* headers. Supporting binary header is an optimization path for GrpcTraceBinContextPropagator | ||
* to work around the lack of binary propagator API and thus avoid | ||
* base64 (de)encoding when passing data between propagator API interfaces. | ||
*/ | ||
final class MetadataGetter implements TextMapGetter<Metadata> { | ||
private static final Logger logger = Logger.getLogger(MetadataGetter.class.getName()); | ||
|
||
private static final MetadataGetter INSTANCE = new MetadataGetter(); | ||
|
||
public static MetadataGetter getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
@Override | ||
public Iterable<String> keys(Metadata carrier) { | ||
return carrier.keys(); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String get(@Nullable Metadata carrier, String key) { | ||
if (carrier == null) { | ||
logger.log(Level.FINE, "Carrier is null, getting no data"); | ||
return null; | ||
} | ||
return carrier.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); | ||
} | ||
|
||
@Nullable | ||
public byte[] getBinary(@Nullable Metadata carrier, String key) { | ||
if (carrier == null) { | ||
logger.log(Level.FINE, "Carrier is null, getting no data"); | ||
return null; | ||
} | ||
return carrier.get(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER)); | ||
} | ||
} |
Oops, something went wrong.