Skip to main content

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

MethodDescriptionParametersReturn Type
Formatter(fmt=None, datefmt=None, style='%')Create formatterfmt: Format string, datefmt: Date format, style: Format styleFormatter

Core Methods

MethodDescriptionParametersReturn Type
format(record)Format log recordrecord: LogRecord instancestr
formatTime(record, datefmt=None)Format timestamprecord: LogRecord, datefmt: Date formatstr
formatException(ei)Format exception infoei: Exception info tuplestr
formatStack(stack_info)Format stack tracestack_info: Stack informationstr

Format Styles

StyleDescriptionExampleNotes
%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

AttributeFormatDescriptionExample
name%(name)sLogger name'myapp.module'
msg%(message)sLog message'User login successful'
argsN/AMessage arguments tuple('user123', 'success')
levelname%(levelname)sLevel name'INFO', 'ERROR'
levelno%(levelno)sLevel number20, 40
pathname%(pathname)sFull pathname'/app/src/main.py'
filename%(filename)sFilename only'main.py'
module%(module)sModule name'main'
lineno%(lineno)dLine number42
funcName%(funcName)sFunction name'process_user'
created%(created)fCreation time1718704245.123456
asctime%(asctime)sFormatted time'2025-06-18 10:30:45,123'
msecs%(msecs)dMilliseconds123
relativeCreated%(relativeCreated)dRelative time (ms)1234
thread%(thread)dThread ID140234567890
threadName%(threadName)sThread name'MainThread'
processName%(processName)sProcess name'MainProcess'
process%(process)dProcess ID12345

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)
  • 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

See Also