Skip to content

JAVA-6222 Support trace context propagation to the server - #2022

Open
nhachicha wants to merge 53 commits into
mongodb:mainfrom
nhachicha:nabil_otel_context
Open

nhachicha wants to merge 53 commits into
mongodb:mainfrom
nhachicha:nabil_otel_context

Conversation

@nhachicha

@nhachicha nhachicha commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

nhachicha added 30 commits June 1, 2026 18:51
Spec proposal (OP_MSG section kind 3, W3C traceparent, hello tracingSupport
negotiation) plus driver-sync POC design to validate it.
Executed end-to-end against mongodb-mongo-master ee3a67f8 (9.0.0-alpha0):
ServerSpanLinkageProseTest passes. Confirmed real server parameters
(featureFlagOtelTraceSampling, openTelemetryTracingSampling BSON doc,
opentelemetryTraceDirectory) and NDJSON export format. Fixed the prose
test to assert operation-span parentage: the driver injects the
traceparent from the OperationContext's operation span (per design 3.1),
so the server span is a sibling of the client command span.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

driver-core/src/main/com/mongodb/internal/operation/ServerVersionHelper.java:38

  • LATEST_WIRE_VERSION is still set to the 8.0 value even though this PR introduces NINE_DOT_ZERO_WIRE_VERSION. This makes the constant name misleading and causes unit tests that use LATEST_WIRE_VERSION to stop representing the newest supported wire version.
    // Server 9.0 (WIRE_VERSION_90 = 29). Minimum wire version for the OP_MSG telemetry
    // section (OTel trace-context propagation, DRIVERS-3454).
    public static final int NINE_DOT_ZERO_WIRE_VERSION = 29;
    public static final int LATEST_WIRE_VERSION = EIGHT_DOT_ZERO_WIRE_VERSION;

Comment thread gradle/libs.versions.toml Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

driver-core/src/main/com/mongodb/internal/operation/ServerVersionHelper.java:37

  • LATEST_WIRE_VERSION is now inconsistent with the newly introduced NINE_DOT_ZERO_WIRE_VERSION constant. Keeping it pinned to 8.0 makes test utilities and any future uses of LATEST_WIRE_VERSION silently exclude wire version 29 features (like OP_MSG telemetry).
    // Server 9.0 (WIRE_VERSION_90 = 29). Minimum wire version for the OP_MSG telemetry
    // section (OTel trace-context propagation, DRIVERS-3454).
    public static final int NINE_DOT_ZERO_WIRE_VERSION = 29;
    public static final int LATEST_WIRE_VERSION = EIGHT_DOT_ZERO_WIRE_VERSION;

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

driver-core/src/main/com/mongodb/internal/operation/ServerVersionHelper.java:38

  • LATEST_WIRE_VERSION is now inconsistent with the newly added NINE_DOT_ZERO_WIRE_VERSION constant (the file comments call 9.0 / wire version 29 the minimum for telemetry support, but LATEST_WIRE_VERSION still points at 8.0 / 25). This makes the meaning of “latest” ambiguous for test setups that import LATEST_WIRE_VERSION.

Consider either updating LATEST_WIRE_VERSION to NINE_DOT_ZERO_WIRE_VERSION, or adjusting naming/docs to clarify why 8.0 remains the “latest” for the driver/test suite.

    // Server 9.0 (WIRE_VERSION_90 = 29). Minimum wire version for the OP_MSG telemetry
    // section (OTel trace-context propagation, DRIVERS-3454).
    public static final int NINE_DOT_ZERO_WIRE_VERSION = 29;
    public static final int LATEST_WIRE_VERSION = EIGHT_DOT_ZERO_WIRE_VERSION;

driver-core/src/main/com/mongodb/internal/connection/CommandMessage.java:221

  • Javadoc has an unbalanced parenthesis: “(first key of the command document.” should be “(first key of the command document).”
     * The command name (first key of the command document. Available before {@code encode()} runs, unlike

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

@nhachicha
nhachicha requested a review from rozza July 23, 2026 14:50
@nhachicha
nhachicha marked this pull request as ready for review July 23, 2026 14:50
@nhachicha
nhachicha requested a review from a team as a code owner July 23, 2026 14:50

@rozza rozza left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - have a few comments / suggestions / questions and nits.

A wider question is should we bump micrometer in versions? Currently we have:

micrometer-tracing = "1.6.0-M3" # This version has a fix for https://github.com/micrometer-metrics/tracing/issues/1092
micrometer-observation-bom = "1.15.4"

Should we just bump to 1.16.6 (latest 1.16.6) or 1.17.0 (latest)?

* key, since encoding only appends fields.
*/
public String getCommandName() {
return command.getFirstKey();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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?

* 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();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Lets annotation this as visible for testing otherwise private:

@VisibleForTesting(otherwise = AccessModifier.PRIVATE)

I would have suggested but imports are annoying with suggestions.

* when {@code micrometer-tracing} is absent from the classpath.
*/
private static final class MicrometerTracingSupport {
@Nullable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding a simple javadoc to help grok this.

Suggested change
@Nullable
/**
* Computes the W3C {@code traceparent} for {@code observation}'s active span.
*
* @param observation the observation whose span supplies the trace context
* @return the {@code traceparent} as specified by {@link TraceContext#traceParent()}, or {@code null} when
* {@code observation} has no span or its trace/span ids are missing, zero, or malformed
*/
@Nullable

* The 55-char W3C {@code traceparent} string for this context
* ({@code 00-<32 hex traceId>-<16 hex spanId>-<flags>}; flags {@code 01} sampled / {@code 00} unsampled),
* or {@code null} when there is no valid context (no-op span, missing/zero/malformed ids).
* Never includes tracestate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +224 to +226
BsonInt64 getMoreCursorId = message.getGetMoreCursorId();
if (getMoreCursorId != null) {
mongodbContext.setCursorId(getMoreCursorId.longValue());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous suggestion for using a Long

Suggested change
BsonInt64 getMoreCursorId = message.getGetMoreCursorId();
if (getMoreCursorId != null) {
mongodbContext.setCursorId(getMoreCursorId.longValue());
Long getMoreCursorId = message.getGetMoreCursorId();
if (getMoreCursorId != null) {
mongodbContext.setCursorId(getMoreCursorId);

Comment on lines +253 to +256
/**
* Walks the OP_MSG sections skip the type-0 body document, then for each subsequent section read the kind byte;
* for kind 1 (document sequence) skip past the {@code int32} section size, and for kind 3 (telemetry) decode and
* return the BSON document payload. Returns {@code null} if no kind-3 section is found.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Slightly easier to read

Suggested change
/**
* Walks the OP_MSG sections skip the type-0 body document, then for each subsequent section read the kind byte;
* for kind 1 (document sequence) skip past the {@code int32} section size, and for kind 3 (telemetry) decode and
* return the BSON document payload. Returns {@code null} if no kind-3 section is found.
/**
* Walks the OP_MSG sections: skips the type-0 body document, then reads the kind byte of each subsequent
* section. For kind 1 (document sequence) it skips past the {@code int32} section size; for kind 3 (telemetry)
* it decodes and returns the BSON document payload. Returns {@code null} if no kind-3 section is found.
*/

*/
public void encode(final ByteBufferBsonOutput bsonOutput, final OperationContext operationContext,
@Nullable final Span commandSpan) {
this.commandSpanForEncoding = commandSpan;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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?

Comment on lines +232 to +235
public BsonInt64 getGetMoreCursorId() {
BsonValue value = command.get("getMore");
return value instanceof BsonInt64 ? (BsonInt64) value : null;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend changing to a Long as that mirrors the getCursorId methods in the code base. See: CommandCursorResult setting of cursorId

Suggested change
public BsonInt64 getGetMoreCursorId() {
BsonValue value = command.get("getMore");
return value instanceof BsonInt64 ? (BsonInt64) value : null;
}
public Long getGetMoreCursorId() {
BsonValue value = command.get("getMore");
return value.isNumber() ? value.asNumber().longValue() : null;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also made a suggestion below to change getGetMoreCursorId use.

@@ -441,20 +439,19 @@ public boolean reauthenticationIsTriggered(@Nullable final Throwable t) {
@Nullable
private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder<T> decoder,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got this feedback from Claude:

🟡 `[important]` — `InternalStreamConnection.java`: test gap

  The connection-level rewiring introduced here has no direct coverage. The new CommandMessage/traceparent tests exercise encode(...) in
  isolation and never construct an InternalStreamConnection, and the existing InternalStreamConnectionSpecification predates these changes.

  Not covered:

    1. That the sync and async paths actually call encode(bsonOutput, operationContext, tracingSpan) (the 3-arg overload) with the span from
       createTracingSpan. A regression to the 2-arg overload or null would pass all current tests yet silently stop emitting the telemetry
       section.
    2. The reorder — span created before encode.
    3. Exactly-once semantics under a send failure: the widened catch (Throwable) firing sendFailedEvent + span.error()/closeScope()/end()
       once each, without the receive path double-firing.
    4. The intentional sync/async asymmetry in the catch (async does no closeScope() / direct sendFailedEvent).

  Notes:

    • End-to-end server acceptance of the kind-3 section is 9.0-gated and reasonably deferred.
    • The wiring / ordering / failure paths are unit-testable with the existing TestInternalConnection + Spock harness and a mocked
      TracingManager/Span.
    • Per AGENTS.md ("every code change must include tests"), this should be covered on a safety-critical send path.

It may be worth auto migrating InternalStreamConnectionSpecification.groovy to Junit 5 and adding extra coverage tests.

nhachicha added a commit to nhachicha/drivers-evergreen-tools that referenced this pull request Sep 3, 2026
…ace-context prose tests

Adds an --otel/OTEL=1 flag to drivers_orchestration.py that starts every
mongod/mongos with the MongoDB 9.0+ OpenTelemetry file-exporter
setParameters (per-port trace directories under $DRIVERS_TOOLS/otel,
samplingFactor 1.0 with raised token buckets for both default and
external sampling per the server IDL), exports OTEL_TRACE_DIR via
mo-expansion.sh/.yml, fails fast on incompatible modes (version < 9.0,
DOCKER_RUNNING, --local-atlas, --mongodb-runner), and wipes the trace
directory between executions. Includes stdlib-unittest coverage, README
documentation with driver integration guidance, and the design/plan docs.

Prerequisite for the prose test in mongodb/specifications#1966;
reference implementation mongodb/mongo-java-driver#2022.
nhachicha added a commit to nhachicha/drivers-evergreen-tools that referenced this pull request Sep 3, 2026
…ace-context prose tests

Adds an --otel/OTEL=1 flag to drivers_orchestration.py that starts every
mongod/mongos with the MongoDB 9.0+ OpenTelemetry file-exporter
setParameters (per-port trace directories under $DRIVERS_TOOLS/otel,
samplingFactor 1.0 with raised token buckets for both default and
external sampling per the server IDL), exports OTEL_TRACE_DIR via
mo-expansion.sh/.yml, fails fast on incompatible modes (version < 9.0,
DOCKER_RUNNING, --local-atlas, --mongodb-runner), and wipes the trace
directory between executions. Includes stdlib-unittest coverage, README
documentation with driver integration guidance, and the design/plan docs.

Prerequisite for the prose test in mongodb/specifications#1966;
reference implementation mongodb/mongo-java-driver#2022.
…th/monitoring spans)

Runs against an OTel-enabled 9.0+ deployment (drivers-evergreen-tools OTEL=1,
DRIVERS-3605); gated on -Dorg.mongodb.test.otel.trace.dir. Parses the exported
OTLP NDJSON batches as JSON; fails explicitly when no span files appear (non-OTel
build) instead of a generic timeout.
Threads OTEL through start-mongo-orchestration (REQUIRE_API_VERSION pattern),
adds otel-tracing-prose-test-task (temporary fork checkout until
mongodb-labs/drivers-evergreen-tools#837 merges), a latest/standalone variant,
and otel trace artifact upload in post.
Test 5 uses insertOne and asserts exactly one parented server span; Test 6
asserts shared traceId and exactly two per-attempt server spans (prose updated
to the retryable-read variant so it runs on all topologies); the CI variant
enables auth so Test 7 exercises the sasl commands as written.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants