Python logging
troubleshooting
logging issues
debugging
Python programming

Python logging not outputting anything

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When Python's logging module produces no output, the cause is almost always one of three things: the logger's level is set higher than the message level (default is WARNING, so logging.info() and logging.debug() are silently dropped), no handler is attached to the logger, or a handler is attached but its own level filters out the message. The fix is usually a single call to logging.basicConfig(level=logging.DEBUG) at the start of your script, or properly configuring a handler with the desired level.

The Problem

python
1import logging
2
3logging.debug("This won't appear")
4logging.info("This won't appear either")
5logging.warning("This WILL appear")  # Only this prints

The root logger's default level is WARNING. Any message below WARNING (i.e., DEBUG and INFO) is silently discarded.

Fix 1: Set the Logging Level

python
1import logging
2
3# Configure at the start of your script
4logging.basicConfig(level=logging.DEBUG)
5
6logging.debug("Debug message")    # Now visible
7logging.info("Info message")      # Now visible
8logging.warning("Warning message")
9logging.error("Error message")

basicConfig(level=logging.DEBUG) sets the root logger to accept all messages at DEBUG and above, and adds a StreamHandler that prints to stderr.

Fix 2: basicConfig Called Too Late

python
1import logging
2
3# BUG: Some library already called basicConfig or added a handler
4logging.warning("First call")  # This triggers auto-configuration
5
6# This has NO effect because basicConfig only works once
7logging.basicConfig(level=logging.DEBUG)
8
9logging.debug("Still won't appear")
10
11# FIX: Call basicConfig BEFORE any logging calls
12import logging
13logging.basicConfig(level=logging.DEBUG)  # Must be first
14logging.debug("Now it works")

basicConfig() does nothing if the root logger already has handlers. Move it to the very top of your entry point, before any imports that might call logging functions.

Fix 3: Force Reconfiguration

python
1import logging
2
3# Force basicConfig to reconfigure even if handlers exist
4logging.basicConfig(level=logging.DEBUG, force=True)  # Python 3.8+
5
6logging.debug("This will appear")
7
8# For Python < 3.8, remove existing handlers manually
9logging.root.handlers = []
10logging.basicConfig(level=logging.DEBUG)

The force=True parameter (Python 3.8+) removes existing handlers before reconfiguring.

Fix 4: Configure Named Loggers Properly

python
1import logging
2
3# Create a named logger (recommended for libraries/modules)
4logger = logging.getLogger(__name__)
5
6# BUG: Named logger has no handler and inherits root logger's WARNING level
7logger.debug("Won't appear")
8
9# FIX: Add a handler and set the level
10logger = logging.getLogger(__name__)
11logger.setLevel(logging.DEBUG)
12
13handler = logging.StreamHandler()
14handler.setLevel(logging.DEBUG)
15
16formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
17handler.setFormatter(formatter)
18
19logger.addHandler(handler)
20
21logger.debug("Now it works")
22# 2025-01-15 10:30:00 - __main__ - DEBUG - Now it works

Named loggers created with getLogger(__name__) start with no handlers and inherit the root logger's level. You must either configure the root logger or add handlers directly.

Fix 5: Level Hierarchy Issues

python
1import logging
2
3# The logger AND the handler each have their own level
4logger = logging.getLogger("myapp")
5logger.setLevel(logging.DEBUG)  # Logger accepts DEBUG+
6
7handler = logging.StreamHandler()
8handler.setLevel(logging.WARNING)  # But handler only outputs WARNING+
9
10logger.addHandler(handler)
11
12logger.debug("Dropped by HANDLER (not logger)")
13logger.info("Also dropped by handler")
14logger.warning("This appears")
15
16# FIX: Set handler level to match or be lower than logger level
17handler.setLevel(logging.DEBUG)

A message must pass both the logger's level and the handler's level to produce output. If either filters it out, nothing is printed.

Fix 6: Logging in Jupyter Notebooks

python
1import logging
2import sys
3
4# Jupyter auto-configures the root logger — basicConfig won't work
5# FIX: Create a fresh handler
6logger = logging.getLogger("notebook")
7logger.setLevel(logging.DEBUG)
8
9# Remove any existing handlers to avoid duplicates
10logger.handlers.clear()
11
12# Add a handler that writes to stdout (Jupyter captures stdout)
13handler = logging.StreamHandler(sys.stdout)
14handler.setLevel(logging.DEBUG)
15handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
16logger.addHandler(handler)
17
18logger.info("Visible in Jupyter")

Jupyter notebooks configure the root logger during startup. Use a named logger with explicitly added handlers to avoid conflicts.

Complete Production Configuration

python
1import logging
2import logging.handlers
3
4def setup_logging():
5    logger = logging.getLogger("myapp")
6    logger.setLevel(logging.DEBUG)
7
8    # Console handler — INFO and above
9    console = logging.StreamHandler()
10    console.setLevel(logging.INFO)
11    console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
12
13    # File handler — DEBUG and above with rotation
14    file_handler = logging.handlers.RotatingFileHandler(
15        "app.log", maxBytes=10_000_000, backupCount=5
16    )
17    file_handler.setLevel(logging.DEBUG)
18    file_handler.setFormatter(logging.Formatter(
19        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
20    ))
21
22    logger.addHandler(console)
23    logger.addHandler(file_handler)
24
25    return logger
26
27logger = setup_logging()
28logger.debug("Only in file")
29logger.info("In console and file")
30logger.error("In console and file")

Common Pitfalls

  • Default level is WARNING: logging.info() and logging.debug() produce no output unless you explicitly set the level to INFO or DEBUG. This catches most beginners.
  • basicConfig() only works once: If any code (including imported libraries) calls a logging function before your basicConfig(), the root logger gets auto-configured with default settings and your basicConfig() is ignored. Use force=True (Python 3.8+) or configure before any imports.
  • Duplicate log messages: Adding a handler every time a function is called (e.g., inside a loop or on every request) causes duplicate output. Check if not logger.handlers before adding, or use logger.handlers.clear().
  • Logger level vs handler level confusion: Both the logger and each handler have independent levels. A message must pass both filters. Setting the logger to DEBUG but the handler to WARNING still drops debug messages.
  • propagate=True causing unexpected output: Named loggers propagate messages to parent loggers by default. If both the named logger and root logger have handlers, messages appear twice. Set logger.propagate = False to prevent propagation.

Summary

  • Call logging.basicConfig(level=logging.DEBUG) before any logging calls
  • The default level is WARNINGDEBUG and INFO messages are silently dropped
  • basicConfig() only works once — use force=True (Python 3.8+) to reconfigure
  • Both logger level and handler level must allow the message for it to appear
  • Use named loggers (getLogger(__name__)) with explicit handlers for production code
  • Set propagate = False on named loggers to avoid duplicate messages

Course illustration
Course illustration

All Rights Reserved.