feat(telemetry): add OTel standard logs as third signal - #239
Open
jeanscherf wants to merge 15 commits into
Open
feat(telemetry): add OTel standard logs as third signal#239jeanscherf wants to merge 15 commits into
jeanscherf wants to merge 15 commits into
Conversation
LucasAlvesSoares
marked this pull request as ready for review
July 28, 2026 16:13
jeanscherf
force-pushed
the
feat/otel-standard-logs
branch
from
August 12, 2026 17:10
d24458c to
bcb47c9
Compare
Wire up a LoggerProvider alongside the existing metrics and traces so
stdlib logging calls automatically flow through OTel with the shared
resource attributes (service.name, region, subaccount, etc.).
- setup_log_provider() in _provider.py mirrors _setup_meter_provider():
LoggerProvider + BatchLogRecordProcessor + OTLP exporter (grpc/http)
- LoggingHandler from opentelemetry-instrumentation-logging installed on
root logger so all existing logging.getLogger() calls are captured
- auto_instrument() calls setup_log_provider() alongside trace setup,
keeping the one-call contract for apps
- user-guide.md: new Logging section with usage examples, structured
fields, level filtering, and trace correlation notes
- unit tests: mock-level coverage for exporter protocol, provider setup,
handler installation, and auto_instrument wiring
- e2e tests: real in-memory pipeline verifying body, severity, resource
attributes, extra={} fields, and trace/span correlation
…wns LoggerProvider When the platform's opentelemetry-instrument wrapper pre-installs a LoggerProvider, our candidate is rejected by get_logger_provider() and the platform's resource is used for all log records — missing sap.cloud_sdk.language, sap.cloud_sdk.name, and sap.cloud_sdk.version. Add _SdkResourceEnrichingProcessor, a LogRecordProcessor wrapper that merges the SDK resource into every ReadWriteLogRecord at on_emit time before forwarding to the inner BatchLogRecordProcessor. Use it in the fallback path of setup_log_provider() when provider is not candidate, so SDK resource attributes always appear in exported records regardless of which LoggerProvider is the global one.
…erProvider opentelemetry-instrument (auto-instrumentation wrapper) calls set_logger_provider() before the application starts. set_logger_provider is a set-once API, so our subsequent call is silently ignored and the platform's provider is used instead. The previous _SdkResourceEnrichingProcessor approach attached a second processor to the provider and a second LoggingHandler to the root logger. Because the platform had already installed both, each log event produced four exported records (2 handlers × 2 processors), with two carrying SDK attrs and two carrying only platform attrs. New approach: - Detect the platform path via isinstance(get_logger_provider(), LoggerProvider). ProxyLoggerProvider (returned before any provider is set) fails this check; the platform's concrete LoggerProvider passes it. - When the platform's provider is already set, mutate its _resource in-place and update all active Logger instances under _active_loggers_lock, exactly as auto_instrument.py does for TracerProvider. This ensures every record emitted by the platform's pipeline carries sap.cloud_sdk.* resource attributes. - Do not add a second processor or handler when one is already present. - Only add processor + handler when no LoggingHandler exists on the root logger (covers the case where the platform set a provider but skipped the handler). Result: exactly one exported record per log event, with all SDK resource attributes.
…umentation When OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true, the platform's sitecustomize.py installs opentelemetry.sdk._logs.LoggingHandler, which is a different class from opentelemetry.instrumentation.logging.handler.LoggingHandler. Our isinstance check in _root_logger_has_otel_handler() missed it, causing us to add a second BatchLogRecordProcessor AND a second LoggingHandler on top of the platform's. Combined with LoggingInstrumentor later adding yet another handler via _instrument_libraries(), this produced 4 records per log event. Fixes: 1. _root_logger_has_otel_handler() now checks both handler classes. 2. Platform path no longer adds a BatchLogRecordProcessor — the platform LP already has one; adding a second one doubles every exported record. 3. LoggingInstrumentorWrapper._instrument() passes enable_log_auto_instrumentation=False when an OTel handler already exists, allowing trace-context injection without adding a duplicate handler.
jeanscherf
force-pushed
the
feat/otel-standard-logs
branch
from
August 13, 2026 20:02
99806b3 to
9853613
Compare
- Restore set_logging_format=True in LoggingInstrumentorWrapper - Update test_exception_returns_none to force the else path so the exception in _create_log_exporter is actually reached - Update clashing-provider test to assert against external_exporter — we no longer add a second processor to the platform LP - Update subaccount_id attribute key after rename on main - Clean up what-comments, keep only the non-obvious why
…rt resource merge
LucasAlvesSoares
approved these changes
Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
LoggerProvideralongside existing metrics and traces using the shared resource attributes (service.name, region, subaccount, etc.)auto_instrument()now sets up all three OTel signals — no app boilerplate changes neededLoggingHandlerfromopentelemetry-instrumentation-logging(not the deprecated SDK one) installed on the root stdlib loggerChanges
_provider.py:setup_log_provider()+_create_log_exporter()— mirrors_setup_meter_provider()pattern, same grpc/http protocol switchauto_instrument.py: onesetup_log_provider()call wired in after trace setupuser-guide.md: new Logging section covering usage,extra={}structured fields, level filtering, trace correlationTests
_create_log_exporter,setup_log_provider, andauto_instrumentwiringextra={}fields, and trace/span correlation — all run in CI without any external dependenciesApp usage
No changes needed. After
auto_instrument(), all existinglogging.getLogger(...)calls ship logs to the OTel backend automatically: