-
Notifications
You must be signed in to change notification settings - Fork 1.5k
JAVA-6222 Support trace context propagation to the server #2022
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
base: main
Are you sure you want to change the base?
Changes from 47 commits
f991058
71944e9
c23e2c9
b7def2e
10a7e48
22460aa
eb27cfa
44fd4ca
9bd5c65
9931e89
d943d9e
0176242
321b7c3
48d76fc
c36118c
9bf631a
f067ee1
9d7cfd6
4c3715f
fe04efd
346a959
e295fa0
6db604c
7352282
b3e5358
dc9fa67
80b0c2f
4c5e5dc
8cade28
7369f1f
e34ba1f
fc850bf
1bd5edf
746de73
b4c8e8c
6c709f7
8f3940a
7ecaf57
b9f56be
b96eef9
baaa522
019f53d
2a31215
8f3db54
f1a4d02
ca3beb0
4f417ff
2ce9337
73cf17b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |||||||||||||||||
| import com.mongodb.internal.MongoNamespaceHelper; | ||||||||||||||||||
| import com.mongodb.internal.TimeoutContext; | ||||||||||||||||||
| import com.mongodb.internal.connection.MessageSequences.EmptyMessageSequences; | ||||||||||||||||||
| import com.mongodb.internal.observability.micrometer.Span; | ||||||||||||||||||
| import com.mongodb.internal.session.SessionContext; | ||||||||||||||||||
| import com.mongodb.lang.Nullable; | ||||||||||||||||||
| import org.bson.BsonArray; | ||||||||||||||||||
|
|
@@ -34,8 +35,11 @@ | |||||||||||||||||
| import org.bson.BsonElement; | ||||||||||||||||||
| import org.bson.BsonInt64; | ||||||||||||||||||
| import org.bson.BsonString; | ||||||||||||||||||
| import org.bson.BsonValue; | ||||||||||||||||||
| import org.bson.ByteBuf; | ||||||||||||||||||
| import org.bson.FieldNameValidator; | ||||||||||||||||||
| import org.bson.codecs.BsonDocumentCodec; | ||||||||||||||||||
| import org.bson.codecs.EncoderContext; | ||||||||||||||||||
| import org.bson.io.BsonOutput; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.io.ByteArrayOutputStream; | ||||||||||||||||||
|
|
@@ -64,6 +68,7 @@ | |||||||||||||||||
| import static com.mongodb.internal.connection.ByteBufBsonDocument.createList; | ||||||||||||||||||
| import static com.mongodb.internal.connection.ByteBufBsonDocument.createOne; | ||||||||||||||||||
| import static com.mongodb.internal.connection.ReadConcernHelper.getReadConcernDocument; | ||||||||||||||||||
| import static com.mongodb.internal.operation.ServerVersionHelper.NINE_DOT_ZERO_WIRE_VERSION; | ||||||||||||||||||
| import static com.mongodb.internal.operation.ServerVersionHelper.UNKNOWN_WIRE_VERSION; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
|
|
@@ -80,6 +85,11 @@ public final class CommandMessage extends RequestMessage { | |||||||||||||||||
| * Specifies that the `OP_MSG` section payload is a sequence of BSON documents. | ||||||||||||||||||
| */ | ||||||||||||||||||
| private static final byte PAYLOAD_TYPE_1_DOCUMENT_SEQUENCE = 1; | ||||||||||||||||||
| /** | ||||||||||||||||||
| * Specifies that the `OP_MSG` section payload is a BSON telemetry document | ||||||||||||||||||
| * ({@code {otel: {traceparent: <string>}}}). | ||||||||||||||||||
| */ | ||||||||||||||||||
| private static final byte PAYLOAD_TYPE_3_TELEMETRY = 3; | ||||||||||||||||||
|
|
||||||||||||||||||
| private static final int UNINITIALIZED_POSITION = -1; | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -101,6 +111,14 @@ public final class CommandMessage extends RequestMessage { | |||||||||||||||||
| private final ClusterConnectionMode clusterConnectionMode; | ||||||||||||||||||
| private final ServerApi serverApi; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * The command span to attach to the OP_MSG telemetry section during {@linkplain | ||||||||||||||||||
| * #encode(ByteBufferBsonOutput, OperationContext, Span) encoding}. Set for the duration of that overload's | ||||||||||||||||||
| * call and cleared afterward; {@code null} otherwise. | ||||||||||||||||||
| */ | ||||||||||||||||||
| @Nullable | ||||||||||||||||||
| private Span commandSpanForEncoding; | ||||||||||||||||||
|
|
||||||||||||||||||
| CommandMessage(final String database, final BsonDocument command, final FieldNameValidator commandFieldNameValidator, | ||||||||||||||||||
| final ReadPreference readPreference, final MessageSettings settings, final ClusterConnectionMode clusterConnectionMode, | ||||||||||||||||||
| @Nullable final ServerApi serverApi) { | ||||||||||||||||||
|
|
@@ -156,15 +174,20 @@ BsonDocument getCommandDocument(final ByteBufferBsonOutput bsonOutput) { | |||||||||||||||||
| byteBuf.position(firstDocumentPosition); | ||||||||||||||||||
| ByteBufBsonDocument byteBufBsonDocument = createOne(byteBuf); | ||||||||||||||||||
|
|
||||||||||||||||||
| // If true, it means there is at least one `PAYLOAD_TYPE_1_DOCUMENT_SEQUENCE` section in the OP_MSG | ||||||||||||||||||
| // If true, there are more sections after the `PAYLOAD_TYPE_0_DOCUMENT` section: either one or more | ||||||||||||||||||
| // `PAYLOAD_TYPE_1_DOCUMENT_SEQUENCE` sections, and/or a trailing `PAYLOAD_TYPE_3_TELEMETRY` section. | ||||||||||||||||||
| if (byteBuf.hasRemaining()) { | ||||||||||||||||||
| BsonDocument commandBsonDocument = byteBufBsonDocument.toBaseBsonDocument(); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Each loop iteration processes one Document Sequence | ||||||||||||||||||
| // When there are no more bytes remaining, there are no more Document Sequences | ||||||||||||||||||
| while (byteBuf.hasRemaining()) { | ||||||||||||||||||
| // skip reading the payload type, we know it is `PAYLOAD_TYPE_1` | ||||||||||||||||||
| byteBuf.position(byteBuf.position() + 1); | ||||||||||||||||||
| byte payloadType = byteBuf.get(); | ||||||||||||||||||
| // Document-sequence sections always precede any trailing non-sequence section (e.g. | ||||||||||||||||||
| // PAYLOAD_TYPE_3_TELEMETRY), and such sections carry no command-document fields, so stop here. | ||||||||||||||||||
| if (payloadType != PAYLOAD_TYPE_1_DOCUMENT_SEQUENCE) { | ||||||||||||||||||
| break; | ||||||||||||||||||
| } | ||||||||||||||||||
| int sequenceStart = byteBuf.position(); | ||||||||||||||||||
| int sequenceSizeInBytes = byteBuf.getInt(); | ||||||||||||||||||
| int sectionEnd = sequenceStart + sequenceSizeInBytes; | ||||||||||||||||||
|
|
@@ -194,6 +217,24 @@ BsonDocument getCommandDocument(final ByteBufferBsonOutput bsonOutput) { | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * The command name (first key of the command document. Available before {@code encode()} runs, unlike | ||||||||||||||||||
| * {@link #getCommandDocument(ByteBufferBsonOutput)}; identical to the encoded document's first | ||||||||||||||||||
| * key, since encoding only appends fields. | ||||||||||||||||||
| */ | ||||||||||||||||||
|
nhachicha marked this conversation as resolved.
|
||||||||||||||||||
| public String getCommandName() { | ||||||||||||||||||
| return command.getFirstKey(); | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth caching this in the constructor? I don't think theres a path where we don't call it? |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * The cursor id if this is a {@code getMore} command, or {@code null} otherwise. | ||||||||||||||||||
| */ | ||||||||||||||||||
| @Nullable | ||||||||||||||||||
| public BsonInt64 getGetMoreCursorId() { | ||||||||||||||||||
| BsonValue value = command.get("getMore"); | ||||||||||||||||||
| return value instanceof BsonInt64 ? (BsonInt64) value : null; | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+232
to
+235
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd recommend changing to a
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also made a suggestion below to change |
||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Get the field name from a buffer positioned at the start of the document sequence identifier of an OP_MSG Section of type | ||||||||||||||||||
| * `PAYLOAD_TYPE_1_DOCUMENT_SEQUENCE`. | ||||||||||||||||||
|
|
@@ -235,6 +276,21 @@ protected void encodeMessageBody(final ByteBufferBsonOutput bsonOutput, final Op | |||||||||||||||||
| this.firstDocumentPosition = useOpMsg() ? writeOpMsg(bsonOutput, operationContext) : writeOpQuery(bsonOutput); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Encodes the message, attaching the OP_MSG telemetry section with {@code commandSpan}'s trace context when | ||||||||||||||||||
| * the gating conditions hold (see {@link #writeTelemetryContextSection}). The 2-arg {@link | ||||||||||||||||||
| * #encode(ByteBufferBsonOutput, OperationContext)} never attaches the section. | ||||||||||||||||||
| */ | ||||||||||||||||||
| public void encode(final ByteBufferBsonOutput bsonOutput, final OperationContext operationContext, | ||||||||||||||||||
| @Nullable final Span commandSpan) { | ||||||||||||||||||
| this.commandSpanForEncoding = commandSpan; | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: This feels like an antipattern - global state that is safe with the try finally but its a bit hidden. Would it be clearer if this was threaded into encode as a parameter instead? |
||||||||||||||||||
| try { | ||||||||||||||||||
| encode(bsonOutput, operationContext); | ||||||||||||||||||
| } finally { | ||||||||||||||||||
| this.commandSpanForEncoding = null; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @SuppressWarnings("try") | ||||||||||||||||||
| private int writeOpMsg(final ByteBufferBsonOutput bsonOutput, final OperationContext operationContext) { | ||||||||||||||||||
| int messageStartPosition = bsonOutput.getPosition() - MESSAGE_PROLOGUE_LENGTH; | ||||||||||||||||||
|
|
@@ -277,11 +333,32 @@ private int writeOpMsg(final ByteBufferBsonOutput bsonOutput, final OperationCon | |||||||||||||||||
| fail(sequences.toString()); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| writeTelemetryContextSection(bsonOutput); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Write the flag bits | ||||||||||||||||||
| bsonOutput.writeInt32(flagPosition, getOpMsgFlagBits()); | ||||||||||||||||||
| return commandStartPosition; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private void writeTelemetryContextSection(final ByteBufferBsonOutput bsonOutput) { | ||||||||||||||||||
| if (getSettings().getMaxWireVersion() < NINE_DOT_ZERO_WIRE_VERSION) { | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| Span commandSpan = this.commandSpanForEncoding; | ||||||||||||||||||
| if (commandSpan == null) { | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| String traceParent = commandSpan.context().traceParent(); | ||||||||||||||||||
| if (traceParent == null) { | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| bsonOutput.writeByte(PAYLOAD_TYPE_3_TELEMETRY); | ||||||||||||||||||
| BsonDocument telemetry = new BsonDocument("otel", | ||||||||||||||||||
| new BsonDocument("traceparent", new BsonString(traceParent))); | ||||||||||||||||||
| new BsonDocumentCodec().encode(new BsonBinaryWriter(bsonOutput), telemetry, | ||||||||||||||||||
| EncoderContext.builder().build()); | ||||||||||||||||||
| } | ||||||||||||||||||
|
nhachicha marked this conversation as resolved.
|
||||||||||||||||||
|
|
||||||||||||||||||
| private int writeOpQuery(final ByteBufferBsonOutput bsonOutput) { | ||||||||||||||||||
| bsonOutput.writeInt32(0); | ||||||||||||||||||
| bsonOutput.writeCString(new MongoNamespace(getDatabase(), MongoNamespaceHelper.COMMAND_COLLECTION_NAME).getFullName()); | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -439,20 +439,19 @@ public boolean reauthenticationIsTriggered(@Nullable final Throwable t) { | |
| @Nullable | ||
| private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder<T> decoder, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I got this feedback from Claude: It may be worth auto migrating |
||
| final OperationContext operationContext) { | ||
| CommandEventSender commandEventSender; | ||
| Span tracingSpan; | ||
| CommandEventSender commandEventSender = new NoOpCommandEventSender(); | ||
| Span tracingSpan = null; | ||
| try (ByteBufferBsonOutput bsonOutput = new ByteBufferBsonOutput(this)) { | ||
| message.encode(bsonOutput, operationContext); | ||
| tracingSpan = operationContext | ||
| .getTracingManager() | ||
| .createTracingSpan(message, | ||
| operationContext, | ||
| () -> message.getCommandDocument(bsonOutput), | ||
| cmdName -> SECURITY_SENSITIVE_COMMANDS.contains(cmdName) | ||
| || SECURITY_SENSITIVE_HELLO_COMMANDS.contains(cmdName), | ||
| () -> getDescription().getServerAddress(), | ||
| () -> getDescription().getConnectionId() | ||
| ); | ||
| message.encode(bsonOutput, operationContext, tracingSpan); | ||
| boolean isLoggingCommandNeeded = isLoggingCommandNeeded(); | ||
| boolean isTracingCommandPayloadNeeded = tracingSpan != null && operationContext.getTracingManager().isCommandPayloadEnabled(); | ||
|
|
||
|
|
@@ -467,27 +466,22 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder | |
| operationContext, message, commandDocument, | ||
| COMMAND_PROTOCOL_LOGGER, loggerSettings); | ||
| commandEventSender.sendStartedEvent(); | ||
| } else { | ||
| commandEventSender = new NoOpCommandEventSender(); | ||
| } | ||
| if (isTracingCommandPayloadNeeded) { | ||
| tracingSpan.setQueryText(commandDocument); | ||
| } | ||
| if (tracingSpan != null) { | ||
| tracingSpan.openScope(); | ||
| } | ||
|
|
||
| try { | ||
| sendCommandMessage(message, bsonOutput, operationContext); | ||
| } catch (Exception e) { | ||
| if (tracingSpan != null) { | ||
| tracingSpan.error(e); | ||
| tracingSpan.closeScope(); | ||
| tracingSpan.end(); | ||
| } | ||
| commandEventSender.sendFailedEvent(e); | ||
| throw e; | ||
| sendCommandMessage(message, bsonOutput, operationContext); | ||
| } catch (Throwable t) { | ||
| if (tracingSpan != null) { | ||
| tracingSpan.error(t); | ||
| tracingSpan.closeScope(); | ||
| tracingSpan.end(); | ||
| } | ||
| commandEventSender.sendFailedEvent(t); | ||
| throw t; | ||
| } | ||
|
|
||
| if (message.isResponseExpected()) { | ||
|
|
@@ -528,7 +522,7 @@ private void sendCommandMessage(final CommandMessage message, final ByteBufferBs | |
| final OperationContext operationContext) { | ||
|
|
||
| Compressor localSendCompressor = sendCompressor; | ||
| if (localSendCompressor == null || SECURITY_SENSITIVE_COMMANDS.contains(message.getCommandDocument(bsonOutput).getFirstKey())) { | ||
| if (localSendCompressor == null || SECURITY_SENSITIVE_COMMANDS.contains(message.getCommandName())) { | ||
| trySendMessage(message, bsonOutput, operationContext); | ||
| } else { | ||
| ByteBufferBsonOutput compressedBsonOutput; | ||
|
|
@@ -617,19 +611,18 @@ private <T> void sendAndReceiveAsyncInternal(final CommandMessage message, final | |
|
|
||
| Span tracingSpan = null; | ||
| try { | ||
| message.encode(bsonOutput, operationContext); | ||
|
|
||
| tracingSpan = operationContext | ||
| .getTracingManager() | ||
| .createTracingSpan(message, | ||
| operationContext, | ||
| () -> message.getCommandDocument(bsonOutput), | ||
| cmdName -> SECURITY_SENSITIVE_COMMANDS.contains(cmdName) | ||
| || SECURITY_SENSITIVE_HELLO_COMMANDS.contains(cmdName), | ||
| () -> getDescription().getServerAddress(), | ||
| () -> getDescription().getConnectionId() | ||
| ); | ||
|
|
||
| message.encode(bsonOutput, operationContext, tracingSpan); | ||
|
|
||
| CommandEventSender commandEventSender; | ||
| boolean isLoggingCommandNeeded = isLoggingCommandNeeded(); | ||
| boolean isTracingCommandPayloadNeeded = tracingSpan != null && operationContext.getTracingManager().isCommandPayloadEnabled(); | ||
|
|
@@ -670,7 +663,7 @@ private <T> void sendAndReceiveAsyncInternal(final CommandMessage message, final | |
|
|
||
| commandEventSender.sendStartedEvent(); | ||
| Compressor localSendCompressor = sendCompressor; | ||
| if (localSendCompressor == null || SECURITY_SENSITIVE_COMMANDS.contains(message.getCommandDocument(bsonOutput).getFirstKey())) { | ||
| if (localSendCompressor == null || SECURITY_SENSITIVE_COMMANDS.contains(message.getCommandName())) { | ||
| sendCommandMessageAsync(message.getId(), decoder, operationContext, tracingCallback, bsonOutput, commandEventSender, | ||
| message.isResponseExpected()); | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -47,11 +47,28 @@ | |||||||||||||||||||
| * @since 5.7 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| public class MicrometerTracer implements Tracer { | ||||||||||||||||||||
| /** | ||||||||||||||||||||
| * {@code micrometer-tracing} is an optional dependency: consumers may have only {@code micrometer-observation} | ||||||||||||||||||||
| * on the classpath (e.g. metrics/logging-only observability setups). Guard any use of {@code io.micrometer.tracing.*} | ||||||||||||||||||||
| * types with this flag so that such setups never trigger a {@link NoClassDefFoundError} or {@link LinkageError}. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| static final boolean MICROMETER_TRACING_ON_CLASSPATH = isMicrometerTracingOnClasspath(); | ||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Lets annotation this as visible for testing otherwise private: I would have suggested but imports are annoying with suggestions. |
||||||||||||||||||||
|
|
||||||||||||||||||||
| private final ObservationRegistry observationRegistry; | ||||||||||||||||||||
| private final boolean allowCommandPayload; | ||||||||||||||||||||
| private final int textMaxLength; | ||||||||||||||||||||
| private final ObservationConvention<MongodbObservationContext> convention; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static boolean isMicrometerTracingOnClasspath() { | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| Class.forName("io.micrometer.tracing.handler.TracingObservationHandler", | ||||||||||||||||||||
| false, MicrometerTracer.class.getClassLoader()); | ||||||||||||||||||||
| return true; | ||||||||||||||||||||
| } catch (ClassNotFoundException | LinkageError e) { | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Constructs a new {@link MicrometerTracer} instance. | ||||||||||||||||||||
| * | ||||||||||||||||||||
|
|
@@ -116,6 +133,68 @@ private static class MicrometerTraceContext implements TraceContext { | |||||||||||||||||||
| MicrometerTraceContext(@Nullable final Observation observation) { | ||||||||||||||||||||
| this.observation = observation; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @Override | ||||||||||||||||||||
| @Nullable | ||||||||||||||||||||
| public String traceParent() { | ||||||||||||||||||||
| if (observation == null) { | ||||||||||||||||||||
| return null; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if (!MICROMETER_TRACING_ON_CLASSPATH) { | ||||||||||||||||||||
| return null; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return MicrometerTracingSupport.traceParent(observation); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * The server ({@code validateW3CTraceparent}) rejects ids that are not exactly the expected | ||||||||||||||||||||
| * length of lowercase hex, or that are all zeroes. Never emit a traceparent it would reject. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| @SuppressWarnings("BooleanMethodIsAlwaysInverted") | ||||||||||||||||||||
| static boolean isValidNonZeroLowercaseHex(@Nullable final String value, final int expectedLength) { | ||||||||||||||||||||
| if (value == null || value.length() != expectedLength) { | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| boolean nonZero = false; | ||||||||||||||||||||
| for (int i = 0; i < value.length(); i++) { | ||||||||||||||||||||
| char c = value.charAt(i); | ||||||||||||||||||||
| if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) { | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if (c != '0') { | ||||||||||||||||||||
| nonZero = true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return nonZero; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Isolates all hard references to {@code io.micrometer.tracing.*} types. This class must only be loaded/invoked | ||||||||||||||||||||
| * after {@link #MICROMETER_TRACING_ON_CLASSPATH} has been confirmed {@code true}, so that JVMs which resolve | ||||||||||||||||||||
| * classes referenced by bytecode eagerly (e.g. during verification) do not trigger a {@link NoClassDefFoundError} | ||||||||||||||||||||
| * when {@code micrometer-tracing} is absent from the classpath. | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| private static final class MicrometerTracingSupport { | ||||||||||||||||||||
| @Nullable | ||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest adding a simple javadoc to help grok this.
Suggested change
|
||||||||||||||||||||
| static String traceParent(final Observation observation) { | ||||||||||||||||||||
| io.micrometer.tracing.handler.TracingObservationHandler.TracingContext tracingContext = | ||||||||||||||||||||
| observation.getContextView().get(io.micrometer.tracing.handler.TracingObservationHandler.TracingContext.class); | ||||||||||||||||||||
| if (tracingContext == null || tracingContext.getSpan() == null) { | ||||||||||||||||||||
| return null; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| // Span.context() is non-null by contract (the package is @NullMarked); a no-op span yields a | ||||||||||||||||||||
| // NOOP context whose empty ids fail the hex validation below. | ||||||||||||||||||||
| io.micrometer.tracing.TraceContext ctx = tracingContext.getSpan().context(); | ||||||||||||||||||||
| String traceId = ctx.traceId(); | ||||||||||||||||||||
| String spanId = ctx.spanId(); | ||||||||||||||||||||
| if (!MicrometerTraceContext.isValidNonZeroLowercaseHex(traceId, 32) | ||||||||||||||||||||
| || !MicrometerTraceContext.isValidNonZeroLowercaseHex(spanId, 16)) { | ||||||||||||||||||||
| return null; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| Boolean sampled = ctx.sampled(); | ||||||||||||||||||||
| return "00-" + traceId + "-" + spanId + (sampled != null && sampled ? "-01" : "-00"); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
|
|
||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.