Skip to content

fix(logging): do not unlink log record factories chained after ours - #4905

Open
UTKARSH698 wants to merge 2 commits into
open-telemetry:mainfrom
UTKARSH698:fix/logging-uninstrument-factory-chain
Open

fix(logging): do not unlink log record factories chained after ours#4905
UTKARSH698 wants to merge 2 commits into
open-telemetry:mainfrom
UTKARSH698:fix/logging-uninstrument-factory-chain

Conversation

@UTKARSH698

Copy link
Copy Markdown
Contributor

Fixes #3808

Problem

logging.setLogRecordFactory is a single global slot. The convention is to chain: read the current factory, close over it, install a wrapper. LoggingInstrumentor._instrument does exactly that.

_uninstrument did not:

def _uninstrument(self, **kwargs):
    if LoggingInstrumentor._old_factory:
        logging.setLogRecordFactory(LoggingInstrumentor._old_factory)
        LoggingInstrumentor._old_factory = None

Restoring _old_factory unconditionally assumes we are still the head of the chain. If an application or another library installed a factory after us, that factory is silently unlinked:

before:  APP ---> OTEL ---> ORIGINAL
after:   APP      OTEL ---> ORIGINAL     # APP is now unreachable

Reproduced on main, instrumenting and then chaining an app factory on top:

before uninstrument -> app_field: present
after  uninstrument -> app_field: MISSING
factory is app_factory?  False
factory is original?     True

The app's factory is gone, and every enrichment it performed is silently lost. As the issue notes, this also bites via instrument(), which calls _uninstrument() internally.

Change

Track the factory we install as _our_factory, and only restore _old_factory while we are still the head of the chain. If something has been chained on top, leave the chain intact and warn — a node cannot be removed from the middle of the chain without the cooperation of the factory that wrapped it, so the honest options are "leave it" or "corrupt it".

This means OTel attributes may persist on records after uninstrument() in the chained case. That is a deliberate trade: the alternative is dropping someone else's factory. The warning explains it.

The unchained case — by far the common one, and the one the existing tests cover — is unchanged: the original factory is fully restored.

Tests

Two tests, both in TestLoggingInstrumentor:

  • test_uninstrument_keeps_factories_chained_after_ours — installs an app factory on top, uninstruments, and asserts the app factory is still the head and still enriches records. Fails on unpatched main (verified by stashing only the source change: 1 failed, 1 passed).
  • test_uninstrument_restores_factory_when_nothing_chained — regression guard for the common path; passes before and after.

pytest tests/test_logging.py goes from 6 failed, 19 passed to 6 failed, 21 passed. The 6 failures are pre-existing on a clean checkout of main in my environment (I had to install released opentelemetry-api/sdk rather than the unreleased 0.66b0.dev core, so there is some version skew locally); they are untouched by this change.

ruff check and ruff format --check clean on both files with the pinned ruff==0.14.1.

Credit

The approach here — tracking _our_factory and only restoring when still at the head — is the one proposed by @stark256-spec in #4651. That PR was reviewed by @xrmx, who asked for a test case and a changelog entry, and was then closed by stale-bot before the author could follow up. This PR reimplements that fix and supplies both missing pieces.

_uninstrument restored _old_factory unconditionally. Python's log record
factory is a single global slot that callers chain by closing over the
previous value, so any factory installed after LoggingInstrumentor was
silently cut out of the chain.

Track the factory we installed and only restore while we are still the head
of the chain. When something has been chained on top, leave the chain alone
and warn: a node cannot be removed from the middle without the cooperation
of the factory that wrapped it.

Fixes open-telemetry#3808
@UTKARSH698
UTKARSH698 requested a review from a team as a code owner July 31, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

opentelemetry-instrumentation-logging: LoggingInstrumentor._uninstrument CORRUPTS the log factory linked list

1 participant