Of course! Let's dive deep into StreamHandler in Python. It's a fundamental component of the logging module, and understanding it is key to effective logging.

What is a StreamHandler?
A StreamHandler is a subclass of logging.Handler. Its primary job is to send log records to a stream—that is, an object that has a write() method.
The most common and default stream used by StreamHandler is sys.stderr (the standard error stream). This is a deliberate choice because sys.stderr is typically unbuffered, meaning log messages will appear immediately, even if your program crashes. This is crucial for debugging.
The Core Concept: Handlers, Loggers, and Formatters
To understand StreamHandler, you must first understand the three pillars of Python's logging system:
- Loggers: These are the entry points for your application. You get a logger instance (e.g.,
logging.getLogger(__name__)) and use methods likelogger.info(),logger.error(), etc. - Handlers: These determine where the log messages go. A logger can have multiple handlers. A
StreamHandlersends messages to the console. Other handlers includeFileHandler(for files),RotatingFileHandler(for files that rotate),SMTPHandler(for emails), etc. - Formatters: These determine the format of the log message. A formatter specifies a string with placeholders for things like the timestamp, log level, logger name, and the message itself.
The flow is simple:
logger -> (passes message to its handlers) -> handler -> (formats the message with its formatter) -> stream (e.g., console)

Basic Usage: The Simplest Example
Let's start with the most basic use case, which is often all you need for simple scripts.
import logging
# 1. Get a logger instance
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Set the lowest severity level this logger will handle
# 2. Create a StreamHandler
# By default, it logs to sys.stderr
stream_handler = logging.StreamHandler()
# 3. (Optional but Recommended) Create a Formatter
# This defines the format of our log messages
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 4. Set the formatter for the handler
stream_handler.setFormatter(formatter)
# 5. Add the handler to the logger
logger.addHandler(stream_handler)
# --- Now, let's log some messages ---
logger.debug("This is a debug message. It might be very detailed.")
logger.info("This is an info message. The application is starting.")
logger.warning("This is a warning message. Something unexpected happened.")
logger.error("This is an error message. A function failed to execute.")
logger.critical("This is a critical message. The program may be unable to continue running.")
Output:
2025-10-27 10:30:00,123 - __main__ - DEBUG - This is a debug message. It might be very detailed.
2025-10-27 10:30:00,123 - __main__ - INFO - This is an info message. The application is starting.
2025-10-27 10:30:00,123 - __main__ - WARNING - This is a warning message. Something unexpected happened.
2025-10-27 10:30:00,123 - __main__ - ERROR - This is an error message. A function failed to execute.
2025-10-27 10:30:00,123 - __main__ - CRITICAL - This is a critical message. The program may be unable to continue running.
Key Parameters and Methods of StreamHandler
Constructor: __init__(stream=None)
You can specify which stream to log to.
stream=None(default): Logs tosys.stderr.stream=sys.stdout: Logs to the standard output stream. This is useful if you want to redirect logs to the console alongside your program's normal output.
Example: Logging to sys.stdout

import logging
import sys
# Create a logger
logger = logging.getLogger("my_app")
logger.setLevel(logging.INFO)
# Create a StreamHandler that logs to stdout
stdout_handler = logging.StreamHandler(stream=sys.stdout)
# Set a simple formatter
formatter = logging.Formatter('%(levelname)s: %(message)s')
stdout_handler.setFormatter(formatter)
logger.addHandler(stdout_handler)
logger.info("This message will appear on standard output.")
Setting the Log Level for the Handler
A logger has a level, and each handler can also have its own level. This is incredibly powerful. It allows you to send different levels of messages to different destinations.
For example, you could log INFO and above to the console but only ERROR and above to a file.
import logging
# Create a logger
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG) # Logger will pass DEBUG and above to its handlers
# Handler 1: Log all levels to the console
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG) # This handler will process DEBUG and above
console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
# Handler 2: Log only errors and above to a file
file_handler = logging.FileHandler('app_errors.log')
file_handler.setLevel(logging.ERROR) # This handler will only process ERROR and above
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.debug("This debug message will only go to the console.")
logger.info("This info message will only go to the console.")
logger.warning("This warning message will only go to the console.")
logger.error("This error message will go to BOTH the console and the file.")
Common Formatter Placeholders
The Formatter class uses a special mini-language to define the message layout. Here are the most common placeholders:
| Placeholder | Description |
|---|---|
%(asctime)s |
Human-readable time when the LogRecord was created. |
%(created)f |
Time when the LogRecord was created, in seconds since the epoch. |
%(levelname)s |
Text logging level for the message (DEBUG, INFO, etc.). |
%(levelno)s |
Numeric logging level for the message (10, 20, etc.). |
%(message)s |
The logged message, computed as msg % args. |
%(name)s |
The name of the logger. |
%(pathname)s |
Full pathname of the source file where the logging call was made. |
%(funcName)s |
Function name. |
%(lineno)d |
Line number in the source file. |
%(process)d |
Process ID. |
%(thread)d |
Thread ID. |
A More Practical, Configurable Example
Manually creating handlers and formatters every time is tedious. The logging.config module allows you to configure your logging setup with a dictionary, which is much cleaner and more maintainable.
This is the recommended approach for any non-trivial application.
import logging.config
import logging
# Define the logging configuration
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s"
},
"simple": {
"format": "%(levelname)s: %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler", # This is the key!
"level": "INFO",
"formatter": "simple",
"stream": "ext://sys.stdout" # Use ext:// for standard streams
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "detailed",
"filename": "app.log",
"maxBytes": 10485760, # 10MB
"backupCount": 5,
"encoding": "utf8",
}
},
"loggers": {
"my_app": {
"level": "DEBUG",
"handlers": ["console", "file"],
"propagate": False # Don't pass messages to parent loggers
}
},
"root": { # The root logger
"level": "WARNING",
"handlers": ["console"]
}
}
# Apply the configuration
logging.config.dictConfig(LOGGING_CONFIG)
# Get the configured logger
logger = logging.getLogger("my_app")
# --- Log messages ---
logger.debug("A detailed debug message, only in the file.")
logger.info("An informational message, to console and file.")
logger.warning("A warning message, to console and file.")
logger.error("An error message, to console and file.")
# The root logger will also work
logging.warning("This is a root logger warning, only 