Skip to content

Safe Self-Tracing via OTel Collector Telemetry Factory#8208

Merged
yurishkuro merged 5 commits into
jaegertracing:mainfrom
yurishkuro:self-tracing
Mar 20, 2026
Merged

Safe Self-Tracing via OTel Collector Telemetry Factory#8208
yurishkuro merged 5 commits into
jaegertracing:mainfrom
yurishkuro:self-tracing

Conversation

@yurishkuro

@yurishkuro yurishkuro commented Mar 20, 2026

Copy link
Copy Markdown
Member

Problem

Jaeger traces its own internal operations (query API, MCP server) by sending spans to an OTLP exporter. When the OTLP receiver in the same Jaeger process is the export destination, every outgoing span triggers another incoming span, causing a recursive self-tracing loop that can flood storage and degrade performance.

Previously, each extension (jaeger_query, remotestorage) independently called jtracer.NewProvider to initialize its own tracer, leading to multiple provider instances and the risk of runaway self-tracing.

External Changes

New behavior:

  • Jaeger no longer creates a recursive self-tracing loop when its OTLP receiver is the
    export destination for internal telemetry.

  • Per-operation sampling rates are now configurable for internal spans via the Jaeger remote sampler:

    OTEL_TRACES_SAMPLER=jaeger_remote
    OTEL_TRACES_SAMPLER_ARG=endpoint=http://localhost:5778/sampling,pollingIntervalMs=5000,initialSamplingRate=0.001
    
  • The standard OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG env vars are honoured for
    all built-in sampler types (always_on, always_off, traceidratio, parentbased_*).
    The default sampler is parentbased_always_on.

Unchanged:

  • enable_tracing: false in the jaeger_query config continues to disable query tracing.
  • All existing OTEL_EXPORTER_OTLP_* and OTEL_TRACES_* environment variables work as before.

Internal Changes

  • Jaeger now uses a single, centrally managed TracerProvider injected through the OTel
    Collector's TelemetryFactory hook.
  • Only explicitly allowlisted extensions (jaeger_query, jaeger_mcp) receive a real
    recording tracer. All other components — receivers, exporters, processors, and unlisted
    extensions — receive a no-op tracer, preventing recursive self-tracing loops.
  • The jaeger_remote sampler lifecycle is now managed correctly: its background polling
    goroutine is stopped on shutdown.
  • Extensions no longer initialize their own tracers; tracing is fully controlled by the
    central factory.

How was this change tested?

  • Unit tests
  • Ran jaeger locally and observed self-tracing working but without any receiver traces.

AI Usage in this PR (choose one)

See AI Usage Policy.

  • None: No AI tools were used in creating this PR
  • Light: AI provided minor assistance (formatting, simple suggestions)
  • Moderate: AI helped with code generation or debugging specific parts
  • Heavy: AI generated most or all of the code changes

Signed-off-by: Yuri Shkuro <github@ysh.us>
Signed-off-by: Yuri Shkuro <github@ysh.us>
Signed-off-by: Yuri Shkuro <github@ysh.us>
Signed-off-by: Yuri Shkuro <github@ysh.us>
Copilot AI review requested due to automatic review settings March 20, 2026 01:42
@yurishkuro yurishkuro requested a review from a team as a code owner March 20, 2026 01:42
@yurishkuro yurishkuro changed the title Self tracing Safe Self-Tracing via OTel Collector Telemetry Factory Mar 20, 2026

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

This PR centralizes Jaeger’s internal “self-tracing” by wiring tracing through the OpenTelemetry Collector TelemetryFactory hook (instead of per-extension manual jtracer.NewProvider setup), and adds support for the jaeger_remote sampler lifecycle.

Changes:

  • Add a Collector telemetry-factory wrapper (WrapFactory) that returns a filtering TracerProvider allowlisting specific component IDs for real tracing.
  • Update jtracer to support OTEL_TRACES_SAMPLER=jaeger_remote and to return a sampler-closer that is invoked during shutdown.
  • Remove manual tracer initialization/shutdown from jaegerquery and remotestorage, and add an ADR + tests covering framework attribute injection and filtering behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/jtracer/jtracer.go Adds jaeger_remote sampler support and ensures sampler lifecycle is closed during provider shutdown.
internal/jtracer/jtracer_test.go Updates tests for new helper signatures returning an additional closer.
cmd/jaeger/internal/telemetry.go Introduces filtering tracer provider + telemetry factory wrapper used by the collector service.
cmd/jaeger/internal/telemetry_test.go Adds unit/integration-style tests validating allowlist behavior and collector attribute injection.
cmd/jaeger/internal/components.go Switches telemetry factory wiring to use WrapFactory(...).
cmd/jaeger/internal/extension/jaegerquery/server.go Removes manual tracer init and preserves enable_tracing behavior by overriding to noop when disabled.
cmd/jaeger/internal/extension/remotestorage/server.go Removes manual tracer init/shutdown and simplifies shutdown handling.
docs/adr/README.md Adds ADR-006 to the ADR index.
docs/adr/006-internal-tracing-via-otelcol-telemetry-factory.md Documents the new centralized self-tracing design and rationale.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +88 to +92
return &filteringTracerProvider{
real: realTP,
noop: nooptrace.NewTracerProvider(),
closer: closer,
}, nil

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

The blocking path returns a standard nooptrace.NewTracerProvider(). That noop tracer still propagates the parent span context into the returned context.Context on Start(), which undermines the goal of preventing incoming trace context from being carried through receiver/processor/exporter code paths (and it also contradicts the ADR text in this PR that calls out the need for a noopNoContextTracerProvider-style implementation). Consider replacing this with a local “no-context noop” TracerProvider/Tracer that returns the original context unchanged from Start(), so blocked components cannot propagate inbound context further downstream.

Copilot uses AI. Check for mistakes.

@yurishkuro yurishkuro Mar 20, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

there is no such thing as noopNoContextTracerProvider. Context is passed explicitly, not by means of a tracer.

func (f *filteringTracerProvider) Shutdown(ctx context.Context) error {
if f.closer != nil {
return f.closer(ctx)
}

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

Shutdown is documented as flushing/shutting down the underlying real TracerProvider, but if closer is nil it currently returns nil without shutting down real. This can leak exporter/span-processor resources if the provider is ever constructed without a closer (including in tests). Consider falling back to calling Shutdown(ctx) on f.real when it implements interface{ Shutdown(context.Context) error }, or adjust the comment to match the actual behavior.

Suggested change
}
}
if s, ok := f.real.(interface{ Shutdown(context.Context) error }); ok {
return s.Shutdown(ctx)
}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

the closer will not be nil if real tracer was returned from creator/factory

Signed-off-by: Yuri Shkuro <github@ysh.us>
@yurishkuro yurishkuro merged commit 6e7eba2 into jaegertracing:main Mar 20, 2026
67 checks passed
@yurishkuro yurishkuro deleted the self-tracing branch March 20, 2026 02:18
@codecov

codecov Bot commented Mar 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.61%. Comparing base (c60dbfb) to head (11bebfd).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
internal/jtracer/jtracer.go 66.66% 4 Missing and 2 partials ⚠️
cmd/jaeger/internal/telemetry.go 92.85% 1 Missing and 1 partial ⚠️
...md/jaeger/internal/extension/jaegerquery/server.go 80.00% 1 Missing ⚠️
.../jaeger/internal/extension/remotestorage/server.go 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8208      +/-   ##
==========================================
- Coverage   95.63%   95.61%   -0.03%     
==========================================
  Files         318      319       +1     
  Lines       16785    16793       +8     
==========================================
+ Hits        16052    16056       +4     
- Misses        578      582       +4     
  Partials      155      155              
Flag Coverage Δ
badger_direct 9.05% <ø> (ø)
badger_e2e 1.04% <ø> (ø)
cassandra-4.x-direct-manual 13.25% <ø> (ø)
cassandra-4.x-e2e-auto 1.03% <ø> (ø)
cassandra-4.x-e2e-manual 1.03% <ø> (ø)
cassandra-5.x-direct-manual 13.25% <ø> (ø)
cassandra-5.x-e2e-auto 1.03% <ø> (ø)
cassandra-5.x-e2e-manual 1.03% <ø> (ø)
clickhouse 1.16% <ø> (ø)
elasticsearch-6.x-direct 16.83% <ø> (ø)
elasticsearch-7.x-direct 16.86% <ø> (ø)
elasticsearch-8.x-direct 17.01% <ø> (ø)
elasticsearch-8.x-e2e 1.04% <ø> (ø)
elasticsearch-9.x-e2e 1.04% <ø> (ø)
grpc_direct 7.79% <ø> (ø)
grpc_e2e 1.04% <ø> (ø)
kafka-3.x-v2 1.04% <ø> (ø)
memory_v2 1.04% <ø> (ø)
opensearch-1.x-direct 16.91% <ø> (ø)
opensearch-2.x-direct 16.91% <ø> (ø)
opensearch-2.x-e2e 1.04% <ø> (ø)
opensearch-3.x-e2e 1.04% <ø> (ø)
query 1.04% <ø> (ø)
tailsampling-processor 0.52% <ø> (ø)
unittests 94.30% <81.81%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants