open-telemetry / open-telemetry/opentelemetry-python-contrib
LoggingInstrumentor: uninstrument() leaves the basicConfig format string asking for otel fields, so every later record fails to format
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 1.1k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 16
Description
Describe your environment
OS: Ubuntu (WSL2)
Python version: 3.14.3
Package version: opentelemetry-instrumentation-logging 0.66b0.dev0, opentelemetry-api 1.45.0.dev0,
built from main at 7704aab58
What happened?
LoggingInstrumentor().instrument(set_logging_format=True) calls
logging.basicConfig(format=..., level=...) with a format string that interpolates %(otelTraceID)s,
%(otelSpanID)s, %(otelServiceName)s and %(otelTraceSampled)s. _uninstrument() restores the log
record factory but leaves that format string in place on the root handler:
def _uninstrument(self, **kwargs):
if LoggingInstrumentor._old_factory:
logging.setLogRecordFactory(LoggingInstrumentor._old_factory)
LoggingInstrumentor._old_factory = None
Once the factory is restored, records no longer carry those attributes — but the handler still asks for
them. Every record logged after uninstrument() fails to format. logging routes the failure
through Handler.handleError, so it is not raised to the caller: the application keeps running and its
log output silently stops, with a traceback per record on stderr.
This is distinct from #3808 / #4905, which are about the factory chain being cut. This one survives
even when nothing else is chained — it is the basicConfig half of _instrument having no counterpart
in _uninstrument.
Steps to Reproduce
import logging
from opentelemetry.instrumentation.logging import LoggingInstrumentor
LoggingInstrumentor().instrument(set_logging_format=True)
logging.getLogger("demo").warning("while instrumented") # fine
LoggingInstrumentor().uninstrument()
logging.getLogger("demo").warning("after uninstrument") # never reaches the stream
Expected Result
After uninstrument(), logging is left as it was found: the record emits normally.
Actual Result
=== after instrument(set_logging_format=True) ===
root handler fmt: ['%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s trace_sampled=%(otelTraceSampled)s] - %(message)s', None]
record factory : record_factory
=== after uninstrument() ===
root handler fmt: ['%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s trace_sampled=%(otelTraceSampled)s] - %(message)s']
record factory : LogRecord
--- Logging error ---
KeyError: 'otelTraceID'
ValueError: Formatting field not found in record: 'otelTraceID'
Formatting the record directly against the surviving handler gives the same
ValueError: Formatting field not found in record: 'otelTraceID'.
Additional context
The restore half was written once already: #4204 ("Saves/restores old state for clean
uninstrumentation") was closed by its author as "Closing pending #4210". #4210 landed — it moved the
handlers into this package and even wraps logging.basicConfig — but it did not carry the
save/restore over, so the gap outlived the PR that was going to close it.
On reach: this only bites code that actually calls uninstrument(), so it is not a
production-hot-path bug. The two places it shows up are test suites that instrument and uninstrument
per test, and applications that toggle instrumentation at runtime. In both, the symptom is confusing
out of proportion to the cause, because the logging stops rather than raising.
Worth noting it interacts with #4905: making the orphaned wrapper a genuine no-op (which that PR is
heading toward) would expose this on the path where the wrapper currently keeps injecting the fields
and thereby keeps the stale format string satisfied. So the ordering probably matters — this one
first, or the two together.
Would you like to implement a fix?
Yes — happy to, if the direction is agreed. The narrow version is to capture the root handler
formatters and level in _instrument when set_logging_format applied, and put them back in
_uninstrument.
AI-assisted with agentic coding tools; all changes were reviewed and tested by me. The output above is
from running the snippet against main at 7704aab58, not reconstructed.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with LoggingInstrumentor._instrument and _uninstrument in the logging instrumentation package, then run the issue's reproduction snippet with set_logging_format=True. Restore the root handler formatter and level after uninstrumentation so a later warning emits normally without missing otel fields; verify the reproduced logging error no longer occurs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- observability
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100