Python logging
log duplication
logging module
output issues
debugging logs

Duplicate log output when using Python logging module

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Duplicate log output in Python usually means the same LogRecord is being handled more than once. The common causes are attaching handlers at multiple levels in the logger hierarchy, reconfiguring logging repeatedly, or mixing application logging setup with library logging setup.

How Duplication Happens

Python logging is hierarchical. A logger named app.api is a child of app, which is a child of the root logger. By default, a child logger propagates records upward. If both the child and one of its ancestors have handlers, one log call can appear multiple times.

This example duplicates output:

python
1import logging
2
3root = logging.getLogger()
4root.setLevel(logging.INFO)
5root.addHandler(logging.StreamHandler())
6
7logger = logging.getLogger("app")
8logger.setLevel(logging.INFO)
9logger.addHandler(logging.StreamHandler())
10
11logger.info("hello")

The app logger handles the record once with its own handler, then propagates the same record to the root logger, which handles it again.

Fix 1: Configure Only One Layer

The cleanest approach is usually to attach handlers only once, often at the root logger, and let child loggers propagate naturally.

python
1import logging
2
3logging.basicConfig(
4    level=logging.INFO,
5    format="%(name)s %(levelname)s %(message)s"
6)
7
8logger = logging.getLogger("app.api")
9logger.info("request started")

Here the child logger has no direct handler, so the record is emitted once by the root configuration.

Fix 2: Disable Propagation When You Intentionally Own the Handler

Sometimes a specific logger should write to its own destination and should not bubble upward. In that case, set propagate to False.

python
1import logging
2
3logger = logging.getLogger("app")
4logger.setLevel(logging.INFO)
5
6handler = logging.StreamHandler()
7handler.setFormatter(logging.Formatter("%(name)s %(message)s"))
8logger.addHandler(handler)
9
10logger.propagate = False
11logger.info("single output")

That stops the record at app and prevents a second emission by the root logger.

Fix 3: Avoid Adding Handlers Repeatedly

Another common duplication bug appears when setup code runs more than once. For example, a module import, test fixture, or web app reload may keep adding new handlers to the same logger.

Bad pattern:

python
1import logging
2
3def configure_logging():
4    logger = logging.getLogger("app")
5    logger.addHandler(logging.StreamHandler())
6    logger.setLevel(logging.INFO)

If configure_logging() runs three times, the logger ends up with three handlers.

Safer version:

python
1import logging
2
3def configure_logging():
4    logger = logging.getLogger("app")
5    logger.setLevel(logging.INFO)
6
7    if not logger.handlers:
8        logger.addHandler(logging.StreamHandler())
9
10    logger.propagate = False
11    return logger
12
13logger = configure_logging()
14logger.info("configured once")

For applications that fully own logging, another option is to clear handlers before rebuilding the configuration.

Libraries Should Not Configure Global Logging Aggressively

If you are writing a library, do not call basicConfig() or attach global handlers as a side effect of import. Libraries should usually create loggers and let the application decide where records go.

python
1import logging
2
3logger = logging.getLogger(__name__)
4
5def do_work():
6    logger.info("library work")

That pattern avoids surprising duplicate or conflicting output in applications that already have a logging policy.

Inspect the Active Configuration

When duplication is confusing, print the relevant state:

python
1import logging
2
3logger = logging.getLogger("app")
4print("logger handlers:", logger.handlers)
5print("propagate:", logger.propagate)
6print("root handlers:", logging.getLogger().handlers)

This quickly reveals whether a logger has its own handler, whether propagation is still enabled, and whether the root logger is also emitting the same record.

Common Pitfalls

The most common mistake is attaching a handler to both a child logger and the root logger without disabling propagation. Another is calling configuration code multiple times in development servers, notebooks, or test runners.

Developers also sometimes use basicConfig() after custom handlers already exist and expect it to replace the old setup. It usually does not behave that way. Library code that configures root logging on import is another frequent source of duplicated or messy output.

Summary

  • Duplicate log lines usually mean one record is being handled more than once.
  • The usual cause is child logger propagation combined with handlers on multiple levels.
  • Prefer one central logging configuration, often on the root logger.
  • If a logger owns its own handler, set propagate = False.
  • Guard against repeated handler registration when configuration code can run more than once.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.