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.
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
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan