Skip to content

feat(python): add set_attributes and custom span/log processors - #859

Open
abelonogov-ld wants to merge 2 commits into
mainfrom
andrey/global-attributes-python
Open

abelonogov-ld wants to merge 2 commits into
mainfrom
andrey/global-attributes-python

Conversation

@abelonogov-ld

@abelonogov-ld abelonogov-ld commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds global attributes and custom span/log processors to the Python observability plugin. This is one of a set of matching PRs across RN, Android, iOS, Flutter, MAUI, Java and Python that share the same semantics.

Global attributes

  • observe.set_attributes(mapping) and observe.remove_attributes(keys) maintain one in-memory, thread-safe attribute map for the whole process (lock + read-only snapshot rebuilt on change). Safe to call before init.
  • Applied to every span (manual, instrumented and exception spans; values are taken when the span starts), every log record (including stdlib logging through the SDK handler), and every metric recorded through observe.record_*.
  • Precedence: per-call attributes > globals > resource. Globals only fill missing keys, and only into remaining attribute-limit capacity, so explicit attributes are never evicted.

Custom processors (init-time options)

  • ObservabilityConfig(span_processors=[...], log_processors=[...]) (opentelemetry.sdk.trace.SpanProcessor, opentelemetry.sdk._logs.LogRecordProcessor).
  • Pipeline: global attributes, then user processors in order, then the SDK's batch exporters, so user changes are exported.
  • Exceptions from user processors are logged and never stop export; force_flush / shutdown are forwarded.

Also: OTel's base SpanProcessor.force_flush returns None, which TracerProvider treats as a failed flush and stops before the exporting processor. The global-attributes processor returns True, and the user-processor wrapper only treats an explicit False as failure, so explicit flushes still reach the exporter.

Note: value validation reuses OTel's opentelemetry.attributes._clean_attribute (private) so globals follow exactly the same rules as span attributes.

How did you test this change?

  • 35 new pytest tests (110 total) with in-memory span, log and metric exporters: merge/remove, read-only snapshots, invalid values, concurrent writers, set-before-init, per-call precedence on spans/logs/metrics, start-time capture, instrumented and exception spans, stdlib logging, processor order with changes exported, raising processors, flush/shutdown forwarding, and the real _OTELConfiguration wiring with network mocked.
  • make lint, make test and poetry build pass.

Are there any deployment considerations?

None. Both features are opt-in and additive; without set_attributes or the new options, behaviour is unchanged.


Note

Overview
Adds process-wide global telemetry attributes and configurable OpenTelemetry processors to the Python observability SDK, with matching e2e coverage in the Flask sample app.

Global attributes: New observe.set_attributes / observe.remove_attributes maintain a thread-safe in-memory map (immutable snapshots) that can be set before plugin init. Globals are merged onto spans at start (via GlobalAttributesSpanProcessor), log records (including stdlib logging), and all observe.record_* metrics. Per-call attributes win over globals; globals only fill missing keys and respect OTel attribute limits.

Custom processors: ObservabilityConfig now accepts span_processors and log_processors. Wiring runs global attributes → user processors (exception-safe wrappers) → batch exporters, with fixes so force_flush still reaches exporters when user processors return None.

Docs and the Flask e2e app document a UTM-style repro (repro.py) that asserts expected attributes on manual span, log, and metric routes. A large new test_global_attributes.py suite covers precedence, concurrency, processor ordering, and export survival on processor errors.

Reviewed by Cursor Bugbot for commit 9201b50. Bugbot is set up for automated code reviews on this repo. Configure here.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
2280 2098 92% 0% 🟢

New Files

File Coverage Status
sdk/@launchdarkly/observability-python/ldobserve/_otel/global_attributes.py 96% 🟢
sdk/@launchdarkly/observability-python/ldobserve/_otel/processors.py 100% 🟢
sdk/@launchdarkly/observability-python/ldobserve/test_global_attributes.py 99% 🟢
TOTAL 98% 🟢

Modified Files

File Coverage Status
sdk/@launchdarkly/observability-python/ldobserve/_otel/configuration.py 87% 🟢
sdk/@launchdarkly/observability-python/ldobserve/config.py 99% 🟢
sdk/@launchdarkly/observability-python/ldobserve/observe.py 81% 🟢
sdk/@launchdarkly/observability-python/ldobserve/test_observe_instance.py 99% 🟢
TOTAL 91% 🟢

updated for commit: 9201b50 by action🐍

Co-authored-by: Cursor <cursoragent@cursor.com>

This branch has not been deployed

No deployments
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