logging.Formatter
The Formatter class is responsible for converting a LogRecord to (usually) a string which can be interpreted by either a human or an external system. The base Formatter allows a formatting string to be specified.
Basic Usage
Simple Formatter
import logging
# Create a basic formatter
formatter = logging.Formatter('%(levelname)s - %(message)s')
# Create handler and set formatter
handler = logging.StreamHandler()
handler.setFormatter(formatter)
# Create logger and add handler
logger = logging.getLogger('my_app')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
# Log messages
logger.info("This is an info message")
# Output: INFO - This is an info message
Detailed Formatter
import logging
# Create detailed formatter with timestamp
formatter = logging.Formatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler = logging.FileHandler('app.log')
handler.setFormatter(formatter)
logger = logging.getLogger('detailed_app')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
logger.error("Something went wrong")
# Output: 2025-06-18 10:30:45 - detailed_app - ERROR - main.py:15 - Something went wrong
Custom Date Format
import logging
# Different date format styles
formatters = [
logging.Formatter('%(asctime)s - %(message)s', '%Y-%m-%d %H:%M:%S'),
logging.Formatter('%(asctime)s - %(message)s', '%d/%m/%Y %I:%M:%S %p'),
logging.Formatter('%(asctime)s - %(message)s', '%B %d, %Y - %H:%M:%S'),
]
for i, formatter in enumerate(formatters):
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger(f'date_test_{i}')
logger.addHandler(handler)
logger.info("Date format test")
# Clear handlers for next iteration
logger.handlers.clear()
Formatter API Reference
Constructor
| Method | Description | Parameters | Return Type |
|---|---|---|---|
Formatter(fmt=None, datefmt=None, style='%') | Create formatter | fmt: Format string, datefmt: Date format, style: Format style | Formatter |
Core Methods
| Method | Description | Parameters | Return Type |
|---|---|---|---|
format(record) | Format log record | record: LogRecord instance | str |
formatTime(record, datefmt=None) | Format timestamp | record: LogRecord, datefmt: Date format | str |
formatException(ei) | Format exception info | ei: Exception info tuple | str |
formatStack(stack_info) | Format stack trace | stack_info: Stack information | str |
Format Styles
| Style | Description | Example | Notes |
|---|---|---|---|
% | Printf-style | '%(levelname)s - %(message)s' | Default, traditional |
{ | str.format() style | '{levelname} - {message}' | Modern Python |
$ | Template style | '$levelname - $message' | Simple substitution |
Format Attributes
LogRecord Attributes
| Attribute | Format | Description | Example |
|---|---|---|---|
name | %(name)s | Logger name | 'myapp.module' |
msg | %(message)s | Log message | 'User login successful' |
args | N/A | Message arguments tuple | ('user123', 'success') |
levelname | %(levelname)s | Level name | 'INFO', 'ERROR' |
levelno | %(levelno)s | Level number | 20, 40 |
pathname | %(pathname)s | Full pathname | '/app/src/main.py' |
filename | %(filename)s | Filename only | 'main.py' |
module | %(module)s | Module name | 'main' |
lineno | %(lineno)d | Line number | 42 |
funcName | %(funcName)s | Function name | 'process_user' |
created | %(created)f | Creation time | 1718704245.123456 |
asctime | %(asctime)s | Formatted time | '2025-06-18 10:30:45,123' |
msecs | %(msecs)d | Milliseconds | 123 |
relativeCreated | %(relativeCreated)d | Relative time (ms) | 1234 |
thread | %(thread)d | Thread ID | 140234567890 |
threadName | %(threadName)s | Thread name | 'MainThread' |
processName | %(processName)s | Process name | 'MainProcess' |
process | %(process)d | Process ID | 12345 |
Format Specifiers
import logging
# Width and alignment
formatter = logging.Formatter('%(levelname)-8s | %(message)s') # Left-align, 8 chars
formatter = logging.Formatter('%(levelname)>8s | %(message)s') # Right-align, 8 chars
# Numeric formatting
formatter = logging.Formatter('%(lineno)04d - %(message)s') # Zero-padded line number
formatter = logging.Formatter('%(msecs)03d - %(message)s') # Zero-padded milliseconds
# Truncation
formatter = logging.Formatter('%(name).20s - %(message)s') # Truncate name to 20 chars
Advanced Formatting
Custom Formatter Class
import logging
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
"""Custom formatter that outputs JSON"""
def format(self, record):
log_entry = {
'timestamp': datetime.fromtimestamp(record.created).isoformat(),
'level': record.levelname,
'logger': record.name,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno
}
# Add exception info if present
if record.exc_info:
log_entry['exception'] = self.formatException(record.exc_info)
return json.dumps(log_entry)
# Usage
json_formatter = JSONFormatter()
handler = logging.StreamHandler()
handler.setFormatter(json_formatter)
logger = logging.getLogger('json_app')
logger.addHandler(handler)
logger.error("JSON formatted error")
Colored Formatter
import logging
class ColoredFormatter(logging.Formatter):
"""Formatter that adds colors to log levels"""
COLORS = {
'DEBUG': '\033[36m', # Cyan
'INFO': '\033[32m', # Green
'WARNING': '\033[33m', # Yellow
'ERROR': '\033[31m', # Red
'CRITICAL': '\033[35m', # Magenta
}
RESET = '\033[0m'
def format(self, record):
# Add color to levelname
if record.levelname in self.COLORS:
record.levelname = (
f"{self.COLORS[record.levelname]}"
f"{record.levelname}"
f"{self.RESET}"
)
return super().format(record)
# Usage (for console output)
colored_formatter = ColoredFormatter(
'%(asctime)s - %(levelname)s - %(message)s'
)
Context-Aware Formatter
import logging
import threading
class ContextFormatter(logging.Formatter):
"""Formatter that includes request context"""
def format(self, record):
# Add request ID from thread-local storage
request_id = getattr(threading.current_thread(), 'request_id', 'N/A')
record.request_id = request_id
# Add user info if available
user_id = getattr(threading.current_thread(), 'user_id', 'Anonymous')
record.user_id = user_id
return super().format(record)
# Usage with custom format
context_formatter = ContextFormatter(
'%(asctime)s [%(request_id)s] [%(user_id)s] %(levelname)s - %(message)s'
)
Format String Examples
Production-Ready Formats
import logging
# Comprehensive production format
PRODUCTION_FORMAT = (
'%(asctime)s.%(msecs)03d [%(process)d:%(thread)d] '
'%(name)-20s %(levelname)-8s %(filename)s:%(lineno)d - %(message)s'
)
# Development format (more readable)
DEVELOPMENT_FORMAT = (
'%(asctime)s %(levelname)-8s [%(name)s] %(funcName)s:%(lineno)d - %(message)s'
)
# Minimal format
MINIMAL_FORMAT = '%(levelname)s: %(message)s'
# Debug format (very detailed)
DEBUG_FORMAT = (
'%(asctime)s.%(msecs)03d %(levelname)-8s '
'[PID:%(process)d TID:%(thread)d] '
'%(name)s.%(funcName)s:%(lineno)d - %(message)s'
)
# Configure based on environment
import os
if os.getenv('DEBUG'):
log_format = DEBUG_FORMAT
elif os.getenv('PRODUCTION'):
log_format = PRODUCTION_FORMAT
else:
log_format = DEVELOPMENT_FORMAT
logging.basicConfig(
level=logging.INFO,
format=log_format,
datefmt='%Y-%m-%d %H:%M:%S'
)
Specialized Formats
# Security audit format
SECURITY_FORMAT = (
'%(asctime)s SECURITY [%(levelname)s] '
'User:%(user_id)s IP:%(client_ip)s Action:%(action)s - %(message)s'
)
# Performance monitoring format
PERFORMANCE_FORMAT = (
'%(asctime)s PERF [%(duration_ms)dms] '
'%(endpoint)s Status:%(status_code)s - %(message)s'
)
# API request format
API_FORMAT = (
'%(asctime)s API [%(method)s] %(url)s '
'Status:%(status)s Duration:%(duration)sms - %(message)s'
)
Performance Considerations
Efficient Formatting
import logging
# Avoid expensive operations in format strings
# BAD: Complex calculations in format
formatter = logging.Formatter('%(asctime)s - Expensive: %(expensive_calc)s - %(message)s')
# GOOD: Pre-calculate values
class EfficientFormatter(logging.Formatter):
def format(self, record):
# Only calculate if actually needed
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
return super().format(record)
# Use lazy evaluation for expensive operations
logger.debug("Expensive operation result: %s", lambda: expensive_operation())
Memory-Efficient Formatting
import logging
from logging import Formatter
class MemoryEfficientFormatter(Formatter):
"""Formatter that reuses string objects"""
def __init__(self, fmt=None, datefmt=None):
super().__init__(fmt, datefmt)
self._cached_formats = {}
def format(self, record):
# Cache formatted strings for common patterns
cache_key = (record.levelname, record.name)
if cache_key not in self._cached_formats:
self._cached_formats[cache_key] = super().format(record)
return self._cached_formats[cache_key]
Common Pitfalls
Format String Errors
import logging
# WRONG: Missing 's' in format specifier
try:
formatter = logging.Formatter('%(levelname) - %(message)s') # Missing 's'
except ValueError as e:
print(f"Format error: {e}")
# WRONG: Non-existent attribute
try:
formatter = logging.Formatter('%(nonexistent)s - %(message)s')
# This will fail at runtime when formatting
except AttributeError as e:
print(f"Attribute error: {e}")
# CORRECT: Always test format strings
formatter = logging.Formatter('%(levelname)s - %(message)s')
test_record = logging.LogRecord(
name='test', level=logging.INFO, pathname='', lineno=0,
msg='test message', args=(), exc_info=None
)
try:
formatted = formatter.format(test_record)
print(f"Format test successful: {formatted}")
except Exception as e:
print(f"Format test failed: {e}")
Unicode and Encoding Issues
import logging
# Handle unicode in messages
class UnicodeFormatter(logging.Formatter):
def format(self, record):
try:
return super().format(record)
except UnicodeError:
# Fallback to safe encoding
record.msg = str(record.msg).encode('utf-8', 'replace').decode('utf-8')
return super().format(record)
Related Components
- logging.Logger - Creates LogRecord objects
- logging.Handler - Uses Formatter to format records
- logging.Filter - Can modify records before formatting
- logging.config - Configuration options for formatters