Log to a file and the console in Python

Attach separate handlers for file and console output, set their thresholds independently, and prevent duplicate records and uncontrolled file growth.

Best practices
Python programming and application logging

Attach a file handler and a stream handler to the same logger. Give each handler its own severity threshold and formatter so the file can retain diagnostic detail while the console stays focused.

Configure both destinations once

import logging

logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
logger.propagate = False

formatter = logging.Formatter(
    "%(asctime)s - %(levelname)s - %(message)s"
)

file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)

logger.addHandler(file_handler)
logger.addHandler(console_handler)

logger.debug("Diagnostic detail")  # File only
logger.info("Service started")    # File and console
logger.error("Operation failed")  # File and console

Run configuration during application startup, not on every request or function call. StreamHandler() uses stderr by default; pass sys.stdout if that is the destination your environment expects.

Understand the two thresholds

For records emitted directly through my_app, its DEBUG threshold allows the record to be created, and each handler then applies its own threshold. Setting the logger to INFO would discard its DEBUG calls before either handler could write them.

The example disables propagation because this logger owns both output handlers. Otherwise the record can also reach handlers on ancestor loggers and appear twice. When records propagate from a child, ancestor logger levels are not reapplied; ancestor handler levels still matter.

Rotate growing files

Replace the file handler with a bounded rotating handler when the process owns the log file:

from logging.handlers import RotatingFileHandler

file_handler = RotatingFileHandler(
    "app.log",
    maxBytes=10 * 1024 * 1024,
    backupCount=5,
    encoding="utf-8",
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)

Use this in place of the original handler before attaching it, rather than adding a second writer to the same file. Standard rotating handlers are not a general solution for multiple processes writing and rotating one file; use a queue/listener, a separate logging service, or your platform's collection approach for that case.

FAQ

Why are DEBUG messages missing? Check the emitting logger's effective level and the destination handler's level, plus any filters or process-wide logging disable setting.

Why do I see duplicates? Look for repeated addHandler() calls and ancestor handlers receiving propagated records. if not logger.handlers can prevent one simple duplicate setup, but it only checks handlers directly attached to that logger and is not a complete configuration strategy.

Can configuration live in a dictionary? Yes. logging.config.dictConfig() describes handlers, formatters, loggers, and their relationships, which is useful when several modules share the same application policy.

References

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting