Skip to content

Conversation

@jasonjkeller
Copy link
Contributor

@jasonjkeller jasonjkeller commented Dec 4, 2025

Resolves #2426

Adds support for OpenTelemetry SpanLinks.

Test suite run: https://github.com/newrelic/newrelic-java-agent/actions/runs/20048117460


Result Of PR

The end result of this PR is that the span_event_data collector payload will now consist of events of type: Span and type: SpanLink. The backend processing pipeline will synthesize Span and SpanLink NRDB events from this data, both of which play a part in the distributed tracing experience.

Backward link to upstream trace:
backward-link

Forward link to downstream traces:
forward-link


How It Works

There are two new methods added to the bridge version of the TracedMethod interface (these aren't available via the public API). These APIs will be implemented on various Tracers (e.g. DefaultTracer). The general idea is that when we intercept an OTel span it triggers the creation of a Tracer, which we now can save all SpanLinks on. When the transaction finishes, all Tracers will be converted into New Relic Spans with any links stored on them. When we harvest all of the Spans that have been sampled, we then separate the Spans and SpanLinks in the JSON wire format.

    /**
     * Add a SpanLink to a collection stored on the traced method.
     * <p>
     * This is used to support the OpenTelemetry concept of a SpanLink,
     * which allows a Span from one trace to link to a Span from
     * a different trace, defining a relationship between the traces.
     *
     * @param link a SpanLink
     */
    void addSpanLink(SpanLink link);

    /**
     * Get a list of SpanLinks associated with this traced method.
     *
     * @return list of SpanLinks
     */
    List<SpanLink> getSpanLinks();

Example Collector Payload

Example span_event_data payload with Span and SpanLink events:

[
    "BR2dwSrqzp...",
    {
        "reservoir_size": 2000,
        "events_seen": 112
    },
    [
        [
            {
                "traceId": "ff45b399d2cc2e6a7a21eb2aec22876a",
                "type": "Span",
                "priority": 1.554158,
                "transactionId": "6b7749a155ded6f3",
                "nr.entryPoint": true,
                "duration": 1.0056225,
                "name": "Java\/org.example.otel.apis.TraceAPI\/upstreamSpan",
                "guid": "2a798d1cc2a6f5c9",
                "transaction.name": "OtherTransaction\/Custom\/org.example.otel.apis.TraceAPI\/upstreamSpan",
                "category": "generic",
                "sampled": true,
                "thread.id": 1,
                "timestamp": 1765215786794
            },
            {},
            {
                "code.namespace": "org.example.otel.apis.TraceAPI",
                "jvm.thread_name": "main",
                "code.function": "upstreamSpan",
                "parent.transportType": "Unknown"
            }
        ],
        [
            {
                "traceId": "ff45b399d2cc2e6a7a21eb2aec22876a",
                "duration": 1.0053169,
                "name": "Span\/upstreamSpan",
                "guid": "d31fdbc473f24309",
                "type": "Span",
                "category": "generic",
                "priority": 1.554158,
                "sampled": true,
                "thread.id": 1,
                "parentId": "2a798d1cc2a6f5c9",
                "transactionId": "6b7749a155ded6f3",
                "timestamp": 1765215786794
            },
            {
                "otel.library.name": "opentelemetry-trace-api-demo",
                "telemetry.sdk.language": "java",
                "otel.scope.name": "opentelemetry-trace-api-demo",
                "service.instance.id": "39296d81-39f0-4ab0-a2e4-e32c474b35f4",
                "service.name": "opentelemetry-api-bridge",
                "entity.guid": "OTk4...",
                "otel.library.version": "1.0.0",
                "otel.scope.version": "1.0.0",
                "telemetry.sdk.version": "1.54.1",
                "telemetry.sdk.name": "opentelemetry"
            },
            {}
        ],
        [
            {
                "traceId": "aded5822728fa2222c42617e88963c41",
                "type": "Span",
                "priority": 1.00045,
                "transactionId": "63c01b231342b7be",
                "nr.entryPoint": true,
                "duration": 1.0031127,
                "name": "Java\/org.example.otel.apis.TraceAPI\/downstreamSpan",
                "guid": "f512c29191b11bf8",
                "transaction.name": "OtherTransaction\/Custom\/org.example.otel.apis.TraceAPI\/downstreamSpan",
                "category": "generic",
                "sampled": true,
                "thread.id": 1,
                "timestamp": 1765215798899
            },
            {},
            {
                "code.namespace": "org.example.otel.apis.TraceAPI",
                "jvm.thread_name": "main",
                "code.function": "downstreamSpan",
                "parent.transportType": "Unknown"
            }
        ],
        [
            {
                "traceId": "aded5822728fa2222c42617e88963c41",
                "duration": 1.002681,
                "name": "Span\/downstreamSpan",
                "guid": "fbdea73443823fec",
                "type": "Span",
                "category": "generic",
                "priority": 1.00045,
                "sampled": true,
                "thread.id": 1,
                "parentId": "f512c29191b11bf8",
                "transactionId": "63c01b231342b7be",
                "timestamp": 1765215798899
            },
            {
                "otel.library.name": "opentelemetry-trace-api-demo",
                "telemetry.sdk.language": "java",
                "otel.scope.name": "opentelemetry-trace-api-demo",
                "service.instance.id": "39296d81-39f0-4ab0-a2e4-e32c474b35f4",
                "service.name": "opentelemetry-api-bridge",
                "entity.guid": "OTk4...",
                "otel.library.version": "1.0.0",
                "otel.scope.version": "1.0.0",
                "telemetry.sdk.version": "1.54.1",
                "telemetry.sdk.name": "opentelemetry"
            },
            {}
        ],
        [
            {
                "trace.id": "aded5822728fa2222c42617e88963c41",
                "linkedSpanId": "d31fdbc473f24309",
                "id": "fbdea73443823fec",
                "type": "SpanLink",
                "linkedTraceId": "ff45b399d2cc2e6a7a21eb2aec22876a",
                "timestamp": 1765215798899
            },
            {
                "customLinkAttribute": "someValue",
                "iteration": 11
            },
            {}
        ]
    ]
]

@jasonjkeller jasonjkeller moved this from Triage to In Progress in Java Engineering Board Dec 8, 2025
@jasonjkeller jasonjkeller self-assigned this Dec 8, 2025
@jasonjkeller jasonjkeller moved this from In Progress to Needs Review in Java Engineering Board Dec 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

2 participants