Skip to content

Split tracing into vendor-neutral observability + OTel adapter#861

Open
KirillKurdyukov wants to merge 2 commits into
mainfrom
tracing-observability-refactor
Open

Split tracing into vendor-neutral observability + OTel adapter#861
KirillKurdyukov wants to merge 2 commits into
mainfrom
tracing-observability-refactor

Conversation

@KirillKurdyukov

Copy link
Copy Markdown
Contributor

Summary

Fixes an architectural issue: tracing was tightly coupled to OpenTelemetry — the SDK forced users to have opentelemetry installed just to enable tracing, and there was no way to plug in a custom backend.

This PR splits the module in two:

  • ydb.observability (new) — vendor-neutral interfaces (Span, TracingProvider), Noop implementation, SpanName, registry, and all SDK-side helpers (create_ydb_span, set_peer_attributes, span_finish_callback, get_trace_metadata). The SDK core imports only from here and never touches opentelemetry.
  • ydb.opentelemetry — becomes a concrete OtelTracingProvider that plugs into the same interface. ydb.opentelemetry.enable_tracing(tracer=None) is now a thin convenience that builds the provider and hands it to ydb.observability.enable_tracing(...).

New public API

from ydb.observability import Span, TracingProvider, enable_tracing, disable_tracing

class MyProvider:
    def create_span(self, name, attributes=None, kind=None) -> Span: ...
    def get_trace_metadata(self): ...

enable_tracing(MyProvider())   # any provider works, no OTel required
enable_tracing(MyProvider())   # second call *replaces* the previous provider
disable_tracing()              # back to Noop
  • Double-enable_tracing cleanly resets the previously installed provider (works for OTel and custom providers alike — previously ydb.opentelemetry.enable_tracing was silently idempotent).
  • ydb.opentelemetry.tracing remains as a re-export shim for backward compatibility.

Tests

  • All 55 existing tracing tests pass (55/55).
  • New tests/tracing/test_observability_enable.py (12 tests) uses a hand-rolled RecordingProviderno OTel involvement — to cover:
    • default Noop state / empty metadata,
    • custom provider receiving spans and metadata,
    • reset on double enable_tracing,
    • enable_tracing(None) == disable_tracing(),
    • disable_tracing reverts to Noop,
    • ydb.opentelemetry.enable_tracing replacing a custom provider,
    • duck-typed provider satisfying the TracingProvider protocol.

Test plan

  • tox -e black-format + tox -e black
  • tox -e style
  • tox -e mypy
  • tox -e py -- ydb -v (174 passed)
  • tox -e py -- tests/tracing -v (55 passed, including 12 new observability tests)
  • Full integration suite (requires Docker; local run)

🤖 Generated with Claude Code

Move all non-OTel tracing plumbing (Span/TracingProvider protocols, Noop
implementation, SpanName, registry, YDB attribute helpers) into a new
ydb.observability package. The OTel bridge in ydb.opentelemetry becomes a
concrete provider (OtelTracingProvider) that plugs into the same interface.

Users can now enable tracing without any OpenTelemetry dependency by
supplying a custom TracingProvider to ydb.observability.enable_tracing. A
second enable call always replaces the previously installed provider.

ydb.opentelemetry.tracing is kept as a thin re-export shim for backward
compatibility. All SDK internal imports (retries, connection, pool,
session, transaction — sync + aio) now go through ydb.observability.tracing.

Added tests/tracing/test_observability_enable.py covering: default Noop
state, custom provider wiring, reset semantics on double enable, disable
reverting to Noop, OTel enable replacing a custom provider, and duck-typed
providers satisfying the protocol.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@KirillKurdyukov KirillKurdyukov requested a review from vgvoleg July 10, 2026 10:18
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.55%. Comparing base (a7bf659) to head (d6aed82).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #861      +/-   ##
==========================================
+ Coverage   81.38%   81.55%   +0.17%     
==========================================
  Files          94       96       +2     
  Lines       12197    12209      +12     
  Branches     1204     1201       -3     
==========================================
+ Hits         9926     9957      +31     
+ Misses       1807     1799       -8     
+ Partials      464      453      -11     
Flag Coverage Δ
integration 79.35% <100.00%> (+0.20%) ⬆️
unit 47.31% <47.26%> (+0.04%) ⬆️

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

Files with missing lines Coverage Δ
ydb/aio/connection.py 88.81% <100.00%> (ø)
ydb/aio/pool.py 75.21% <100.00%> (ø)
ydb/aio/query/session.py 88.60% <100.00%> (ø)
ydb/aio/query/transaction.py 100.00% <100.00%> (ø)
ydb/connection.py 80.46% <100.00%> (ø)
ydb/observability/__init__.py 100.00% <100.00%> (ø)
ydb/observability/tracing.py 100.00% <100.00%> (ø)
ydb/opentelemetry/__init__.py 100.00% <ø> (+30.76%) ⬆️
ydb/opentelemetry/plugin.py 100.00% <100.00%> (+5.88%) ⬆️
ydb/opentelemetry/tracing.py 100.00% <100.00%> (+10.67%) ⬆️
... and 4 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

  - TestSplitEndpoint — parametric test covering every branch of _split_endpoint (grpcs://, grpc://, bare host:port, valid/malformed IPv6, missing colon, non-numeric port, None).
  - TestBuildYdbAttrsPeerOptionals — peer tuples with only address / only port / only location / empty location, hitting the three if x is not None branches in _build_ydb_attrs.
  - TestSetPeerAttributes — direct unit test for the set_peer_attributes helper (previously only reached through session flows that never had a non-None peer).
  - TestSpanFinishCallback — success and exception paths of the streaming _finish callback.
  - TestOpentelemetryTracingShimReexports — proves the ydb.opentelemetry.tracing back-compat shim re-exports the exact same objects as ydb.observability.tracing.
  - TestOtelEntrypointHandlesMissingPackage — uses sys.modules["ydb.opentelemetry.plugin"] = None to force the ImportError branches in ydb.opentelemetry.__init__.enable_tracing / disable_tracing.
  - TestOtelTracingSpanBridge — direct tests for TracingSpan.set_attribute and the defensive "exit before enter" branch in _AttachContext.
  - TestNoopContextManager — smoke tests for the Noop context manager and NoopTracingProvider returning the shared span + empty metadata.

Copilot AI left a comment

Copy link
Copy Markdown

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 decouples SDK tracing from OpenTelemetry by introducing a vendor-neutral ydb.observability layer (interfaces + registry + helpers) and turning ydb.opentelemetry into a concrete adapter that installs an OtelTracingProvider via the new interface. This allows tracing to work without opentelemetry installed unless the user explicitly opts into the OTel adapter.

Changes:

  • Add ydb.observability with Span/TracingProvider protocols, a Noop provider, a provider registry, and shared SDK tracing helpers (create_ydb_span, metadata injection, etc.).
  • Refactor SDK core (sync + async) to import tracing helpers from ydb.observability instead of ydb.opentelemetry.
  • Rework ydb.opentelemetry into an adapter (OtelTracingProvider) and keep ydb.opentelemetry.tracing as a backward-compatible re-export shim; add new tests and update docs/changelog.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
ydb/retries.py Switch internal retry spans to ydb.observability.tracing.
ydb/query/transaction.py Switch query transaction tracing helpers to ydb.observability.tracing.
ydb/query/session.py Switch query session tracing helpers to ydb.observability.tracing.
ydb/pool.py Switch pool tracing helpers to ydb.observability.tracing.
ydb/connection.py Switch trace metadata injection source to ydb.observability.tracing.
ydb/aio/query/transaction.py Maintain sync/async parity by switching async transaction tracing imports.
ydb/aio/query/session.py Maintain sync/async parity by switching async session tracing imports.
ydb/aio/pool.py Maintain sync/async parity by switching async pool tracing imports.
ydb/aio/connection.py Maintain sync/async parity by switching async metadata injection imports.
ydb/observability/tracing.py New vendor-neutral tracing protocols, Noop impl, registry, and shared SDK helpers.
ydb/observability/init.py New public observability entrypoints (enable_tracing, disable_tracing, exports).
ydb/opentelemetry/plugin.py Implement OtelTracingProvider adapter and route enable/disable through observability.
ydb/opentelemetry/init.py Keep OTel entrypoints with lazy import and improved “replace provider” semantics.
ydb/opentelemetry/tracing.py Preserve backward-compatible import paths via re-export shim to observability.
tests/tracing/test_tracing_sync.py Update tracing tests to use observability registry and helpers.
tests/tracing/test_tracing_async.py Update async tracing tests to import from observability.
tests/tracing/test_observability_enable.py Add new unit tests for provider swapping/Noop behavior without OTel.
docs/opentelemetry.rst Document provider replacement semantics and the new vendor-neutral API.
CHANGELOG.md Note new ydb.observability entrypoint and removal of core OTel import dependency.

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

Comment on lines +7 to +10
from ydb.observability import enable_tracing
from ydb.opentelemetry import OtelTracingProvider

enable_tracing(OtelTracingProvider()) # or any custom TracingProvider
Comment on lines +28 to +35
def enable_tracing(provider: TracingProvider) -> None:
"""Install *provider* as the active tracing backend.

Calling this a second time replaces the previous provider — the old one
stops receiving spans immediately. Passing ``None`` is equivalent to
:func:`disable_tracing`.
"""
_registry.set_provider(provider)
Comment on lines +117 to +120
def get_trace_metadata(self) -> Iterable[Tuple[str, str]]:
headers: dict = {}
inject(headers)
return tuple(headers.items())

@robot-vibe-db robot-vibe-db Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI Review Summary

Verdict: ✅ No critical issues found

Critical issues

No critical issues found.

Other findings

  • Minor | High: Incorrect import path in docstring — ydb/observability/__init__.py:9
  • Minor | Medium: enable_tracing type annotation rejects None but implementation and docs accept it — ydb/observability/__init__.py:28
  • Minor | Medium: Backward-compat shim re-exports _registry but its type changed from OtelTracingRegistry to _TracingRegistry with different methods — ydb/opentelemetry/tracing.py:17
  • Nit | Low: Span.attach_context return type is Any instead of ContextManagerydb/observability/tracing.py:39

This review was generated automatically. Critical issues require attention; other findings are advisory.
If this comment was useful, please give it a 👍 — it helps us improve the review bot.


from ydb.observability import enable_tracing
from ydb.opentelemetry import OtelTracingProvider

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: Minor
Confidence: High

The docstring suggests from ydb.opentelemetry import OtelTracingProvider, but OtelTracingProvider is only defined in ydb.opentelemetry.plugin and is not re-exported from ydb.opentelemetry.__init__. This import will raise ImportError at runtime.

Suggested fix — either:

  1. Change the docstring to from ydb.opentelemetry.plugin import OtelTracingProvider, or
  2. Add OtelTracingProvider to ydb/opentelemetry/__init__.py's namespace (and __all__).

)


def enable_tracing(provider: TracingProvider) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: Minor
Confidence: Medium

The type annotation declares provider: TracingProvider, but the docstring on line 33 says "Passing None is equivalent to disable_tracing()", and _registry.set_provider accepts Optional[TracingProvider]. The test at test_observability_enable.py:193 calls enable_tracing(None) with a # type: ignore comment.

Consider changing the signature to provider: Optional[TracingProvider] so the documented behavior is expressible without type: ignore, or remove the "Passing None" sentence from the docstring if the intent is to keep None as an undocumented escape hatch.

TracingProvider,
_NoopCtx,
_build_ydb_attrs,
_registry,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: Minor
Confidence: Medium

The backward-compat shim re-exports _registry, but its type has changed from OtelTracingRegistry to _TracingRegistry. The old registry exposed set_metadata_hook() and set_create_span(); the new one exposes set_provider() and get_provider() instead. Code that directly called _registry.set_create_span(...) or _registry.set_metadata_hook(...) (or imported OtelTracingRegistry) will break.

All affected names are private (_-prefixed), so this is acceptable, but it may be worth noting in the changelog or migration notes that direct use of the internal registry API is no longer supported.


def end(self) -> None: ...

def attach_context(self, end_on_exit: bool = True) -> Any:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: Nit
Confidence: Low

attach_context returns Any. A more precise return type (e.g. ContextManager["Span"] from typing) would give static-analysis tools and IDE users better guidance about the returned context manager.

@robot-vibe-db

robot-vibe-db Bot commented Jul 13, 2026

Copy link
Copy Markdown

Full analysis log

Analysis performed by claude, claude-opus-4-6.

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