Skip to content

feat(tracing): send OTLP traces to the target environment's collector#650

Merged
LinoGiger merged 1 commit into
mainfrom
feat(tracing)/send-otlp-traces-to-target-environment
Jul 6, 2026
Merged

feat(tracing): send OTLP traces to the target environment's collector#650
LinoGiger merged 1 commit into
mainfrom
feat(tracing)/send-otlp-traces-to-target-environment

Conversation

@RapidPoseidon

Copy link
Copy Markdown
Contributor

What

SDK traces (and logs) only ever reached the prod collector. Two things blocked non-prod:

  1. RapidataClient.__init__ disabled OTLP entirely whenever environment != "rapidata.ai".
  2. The trace/log exporters hard-coded https://otlp-sdk.rapidata.ai/v1/{traces,logs}.

This derives the collector host from the client's environmentotlp-sdk.<environment> — the same way api.<env> / auth.<env> are built, and enables OTLP for the environments that actually have a deployed collector.

Environments

Environment environment string Collector Traces
prod rapidata.ai otlp-sdk.rapidata.ai ✅ (unchanged)
test rabbitdata.ch otlp-sdk.rabbitdata.ch ✅ now on
dev (local) rapidata.dev ⏸️ left off (no collector)

The test collector already exists — infrastructure's observability/overlays/test/httproute-otlp-sdk.yaml routes otlp-sdk.rabbitdata.ch, and POST /v1/traces returns 200 (verified against both prod and test). Local dev is intentionally excluded (per request): its local collector is exposed under a different host (otlp.rapidata.dev) and uses a self-signed cert, so enabling it there would only produce failed exports. Unknown environments stay off for the same reason.

How

  • LoggingConfig gains an environment field (the existing config-update channel the tracer/logger already listen on).
  • RapidataTracer / RapidataLogger build their endpoint from that value.
  • RapidataClient sets environment on the config and enables OTLP only for _OTLP_COLLECTOR_ENVIRONMENTS (prod + test).

Checks

  • pyright src/rapidata/rapidata_client → 0 errors.
  • Verified endpoint derivation + gating for prod/test/dev.

🔗 Session: https://session-7f001dc5.poseidon.rapidata.internal/

The SDK tracer and logger hard-coded the prod collector
(otlp-sdk.rapidata.ai) and RapidataClient disabled OTLP entirely for any
non-prod environment, so traces from the test environment were silently
dropped.

Derive the collector host from the client's environment
(otlp-sdk.<environment>), matching how the api./auth. endpoints are built,
and enable OTLP for environments that actually have a deployed collector
(prod rapidata.ai + test rabbitdata.ch). Local dev (rapidata.dev) has no
collector, so it stays disabled to avoid noisy failed exports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: lino <lino@rapidata.ai>
@claude

claude Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review

Nice, focused fix — deriving the collector host from environment (mirroring the api.<env> / auth.<env> pattern) is the right approach, and gating to prod+test only is sensible given dev has no reachable collector.

Bug: OTLP endpoint is locked to whichever environment first initializes it, for the life of the process

RapidataTracer._ensure_initialized() (tracer.py:152-181) and RapidataLogger._ensure_otlp_initialized() (logger.py:58-102) are both guarded by a one-shot flag:

if self._otlp_initialized:
    return
...
exporter = OTLPSpanExporter(endpoint=f"https://otlp-sdk.{self._environment}/v1/traces", ...)
...
self._otlp_initialized = True

tracer/logger are process-wide singletons, and _update_tracer/_update_logger update self._environment on every config change — but the exporter/provider is only built once, on the first span/log emitted. Once that happens, later changes to self._environment no longer take effect because the early-return skips rebuilding the exporter.

Concretely: if a process creates RapidataClient(environment="rabbitdata.ch") and emits any span first, then later creates RapidataClient(environment="rapidata.ai"), the second client's traces will still be silently exported to the test collector (otlp-sdk.rabbitdata.ch), not prod — because _otlp_initialized is already True from the first client. This is exactly the multi-environment scenario this PR is meant to support (prod + test both now live), so it seems likely to actually bite. Before this PR the endpoint was a constant, so the one-shot init guard was harmless; now that the endpoint is derived from mutable state, the guard needs to either rebuild the exporter when self._environment changes, or the tracer/logger need to be scoped per-client instead of process-global singletons.

Related: enable_otlp is only ever forced off, never back on

rapidata_config.logging.environment = environment
if environment not in _OTLP_COLLECTOR_ENVIRONMENTS:
    rapidata_config.logging.enable_otlp = False

This only sets enable_otlp = False for non-collector environments; it never sets it back to True when a subsequent client targets a collector environment. So RapidataClient(environment="rapidata.dev") followed by RapidataClient(environment="rapidata.ai") in the same process leaves OTLP disabled for the second client too. This pre-dates this PR (same one-directional pattern existed for the old != "rapidata.ai" check), but it's worth fixing alongside the issue above since both stem from the same root cause: rapidata_config/tracer/logger are shared globals that don't cleanly support multiple RapidataClient instances with different environments in one process.

Minor / non-blocking

  • No test coverage was added for the new gating/endpoint-derivation logic (I see there's no existing test suite for the config module either, so this may be consistent with current practice — but this is exactly the kind of gating logic that's easy to silently regress).
  • docs/config.md's "Logging Configuration Options" table doesn't mention the new environment field. Probably fine to omit since it's SDK-managed rather than user-facing, but flagging per the CLAUDE.md guidance to keep docs in sync with interface changes.

Everything else (the endpoint derivation itself, the _OTLP_COLLECTOR_ENVIRONMENTS frozenset, defaulting _environment = "rapidata.ai" on both RapidataTracer/RapidataLogger) looks correct and consistent with the existing api.<env>/auth.<env> convention.

@LinoGiger LinoGiger marked this pull request as ready for review July 6, 2026 15:38
@LinoGiger LinoGiger self-requested a review as a code owner July 6, 2026 15:38
@LinoGiger LinoGiger merged commit 30c05b1 into main Jul 6, 2026
2 checks passed
@LinoGiger LinoGiger deleted the feat(tracing)/send-otlp-traces-to-target-environment branch July 6, 2026 15:38
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.

2 participants