Python logging not printing to console or file (tested)
Python logging that prints nothing, or never creates its log file, almost always comes down to one of five things. We measured each on Python 3.13.5.
1. Nothing configured: only WARNING and above show
import logging
log = logging.getLogger("app")
log.debug("debug message")
log.info("info message")
log.warning("warning message")
print("effective level:", logging.getLevelName(log.getEffectiveLevel()))
Output:
warning message
effective level: WARNING
Measured on Python 3.13.5: with no configuration, the effective level is WARNING, so debug() and info() print nothing. The warning that does appear comes from logging's built-in "last resort" handler (bare message, no level prefix, on stderr). Call logging.basicConfig(level=logging.INFO) once at startup.
2. basicConfig() silently does nothing the second time
import logging
logging.warning("an import or library logged first") # implicitly calls basicConfig()
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s %(message)s") # silently ignored
logging.getLogger("app").info("info message")
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s %(message)s", force=True) # fix
logging.getLogger("app").info("info message after force=True")
Output:
WARNING:root:an import or library logged first
INFO info message after force=True
Measured: any call like logging.warning() on the root logger (often inside an imported library) runs basicConfig() implicitly. After that, your own basicConfig(level=DEBUG, ...) is a no-op with no error, so your level and format never apply. force=True (3.8+) removes the existing handlers and applies yours.
3. Setting the handler's level isn't enough
import logging, sys
log = logging.getLogger("app")
h = logging.StreamHandler(sys.stdout)
h.setLevel(logging.DEBUG)
log.addHandler(h)
log.info("handler is DEBUG, logger is not set") # dropped: logger level wins first
log.setLevel(logging.DEBUG)
log.info("now the logger is DEBUG too")
Output:
now the logger is DEBUG too
Measured: a record must pass the logger's level before any handler sees it. The handler was DEBUG but the logger was still at the inherited WARNING, so the first info() was dropped. Set both: log.setLevel(logging.DEBUG).
4. Not writing to a file
import logging, os
os.makedirs("/tmp/lg/run", exist_ok=True); os.chdir("/tmp/lg/run")
logging.warning("something logged before setup")
logging.basicConfig(filename="app.log", level=logging.INFO) # ignored: root already has a handler
logging.info("should go to app.log")
print("app.log exists:", os.path.exists("app.log"))
logging.basicConfig(filename="app.log", level=logging.INFO, force=True)
logging.info("goes to app.log")
logging.shutdown()
print("app.log:", open("app.log").read().strip())
print("relative to cwd:", os.path.abspath("app.log"))
Output:
WARNING:root:something logged before setup
app.log exists: False
app.log: INFO:root:goes to app.log
relative to cwd: /tmp/lg/run/app.log
Measured: basicConfig(filename="app.log") after an earlier log call creates no file at all, for the same reason as section 2; force=True fixes it. A relative filename is resolved against the current working directory, not the script's folder, so under cron, systemd or an IDE the file may be somewhere you aren't looking. Use an absolute path. Output is written as each record is logged; logging.shutdown() (run automatically at exit) closes the file.
5. The opposite: every message printed twice
import logging, sys
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format="%(name)s: %(message)s")
log = logging.getLogger("app")
log.addHandler(logging.StreamHandler(sys.stdout)) # one more handler
log.info("printed twice")
log.propagate = False
log.info("printed once")
Output:
printed twice
app: printed twice
printed once
Measured: a handler on app plus a handler on the root logger prints each record twice, because records propagate up to the root. Add handlers in one place only, or set propagate = False on the child logger.
Frequently asked questions
Why is Python logging not printing to the console?
The default level is WARNING, so info() and debug() are dropped. We measured only the warning appearing on Python 3.13.5. Call logging.basicConfig(level=logging.INFO) at startup.
Why does logging.basicConfig not work?
It does nothing if the root logger already has a handler, which an earlier logging.warning() call (often in a library) creates implicitly. We measured the second call being ignored; basicConfig(..., force=True) fixes it.
Why is Python logging not writing to file?
Either basicConfig was ignored (no file was created in our test until force=True) or the relative filename went to a different working directory. Use force=True and an absolute path.
Why does my log message print twice?
A handler on your logger plus one on the root logger, with propagation on. We measured the duplicate and propagate = False removing it.
Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux: the default WARNING level and last-resort handler, basicConfig ignored after an implicit call and force=True, logger vs handler levels, a file handler that never creates its file, relative log paths, and duplicate output from propagation. Every block was run verbatim and its output pasted from that run (stdout and stderr together). PlainHeaders is our extension; everything else here is the standard library.