← Debugging Guides

阅读中文版本

Python — Log Messages Appear Multiple Times

Fix duplicated Python log lines by finding repeated addHandler calls and propagating records between loggers, then configure handlers once at the root.

beginnerdebuggingpythonloggingstdlib

Symptoms

  • Each log line appears twice, three times, or more with identical timestamps and content
  • The multiplication gets worse the longer the process runs or the more modules import the logging setup
  • Duplicate lines carry the same logger name but the count grows with re-imports or re-initialization

Possible causes

  • A module calls logging.basicConfig or logger.addHandler at import time, so each import adds another handler
  • A re-initialization path (hot reload, reconnect, test fixture) adds handlers without clearing existing ones
  • Child loggers propagate records to ancestor loggers whose handlers also emit, printing once per attached handler
  • Both a library default handler and the application handler are active on the same logger

Diagnosis steps

Count handlers per logger

python -c "import logging; print(logging.getLogger('app').handlers); print(logging.getLogger().handlers)"

Interpretation

More than one handler on the emitting logger or its ancestors is the mechanism; the list length predicts the duplicate count.

Find registration at import time

grep -Rn "addHandler\|basicConfig" src/ | head -20

Interpretation

Any module-level call runs on every import of that module, and test collection imports it repeatedly.

Confirm propagation shape

python -c "import logging; l=logging.getLogger('a.b'); print(l.parent.name, l.propagate)"

Interpretation

A child logger with propagate=True and a parent with handlers emits once per handler up the chain.

Root cause

A log record is emitted once per handler attached to the emitting logger and, with propagation enabled, once per handler on every ancestor. Repeated addHandler calls — typically at import time or in re-initialization paths — stack multiple handlers, so one record prints once per stacked handler.

Fix

Configure handlers once at the program entry point on the root logger, guard library setup against re-entry, clear existing handlers before reconfiguring, and set propagate=False only for deliberate isolation such as access logs.

Risk note

Blindly setting propagate=False on every logger hides records from centralized handlers. Use it for intentional streams, not as a deduplication fix.

Verify the fix

Emit one record after a fresh start and after a simulated re-import; assert exactly one line in captured output each time.

Symptom walkthrough

Identical timestamps and content on consecutive lines rule out duplicate emission and point at duplicate delivery:

2026-10-22 09:14:03 INFO app.jobs job finished
2026-10-22 09:14:03 INFO app.jobs job finished

Interrogate the logger to see the multiplication directly:

import logging
print(logging.getLogger("app.jobs").handlers)   # [..StreamHandler]
print(logging.getLogger("app").handlers)          # [..StreamHandler, ..FileHandler]

The total handler count across the logger and its ancestors equals the number of printed copies. The count growing with process lifetime or test collection identifies import-time registration as the source.

Fix in practice

Centralize configuration at the entry point, and make reconfiguration idempotent by clearing first:

import logging

def setup_logging():
    root = logging.getLogger()
    root.setLevel(logging.INFO)
    for h in list(root.handlers):
        root.removeHandler(h)          # idempotent re-entry
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
    root.addHandler(handler)

In libraries, never attach handlers — emit records and let the application decide delivery. When a module must configure logging for standalone use, guard it:

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)

Use propagate=False only for deliberately isolated streams (access logs to their own file), and document the choice.