- What is logging — tracking events in your code
- Why use logging — better than print statements
- Log levels — DEBUG, INFO, WARNING, ERROR, CRITICAL
- Basic logging — getting started quickly
- Advanced logging — handlers, formatters, filters
- Real-world examples — logging in applications
What is Logging?
Logging is the practice of recording events that happen in your program. It's like keeping a diary of what your code does — when it starts, what it processes, if there are errors, and when it finishes.
Think of logging like a flight recorder in an airplane. It records everything that happens during the flight. If something goes wrong, you can look at the logs to figure out what happened. Logging works the same way for your code.
Python has a built-in logging module that's much better than using print() statements for debugging.
💡 Key concept: Logging is a way to record events in your program for debugging, monitoring, and troubleshooting.
Why Use Logging?
Logging vs Print
Logging is much better than using print() for debugging. Here's why.
# Why Use Logging?
print("=" * 50)
print("WHY USE LOGGING?")
print("=" * 50)
# ============================================================
# USING PRINT - Problems
# ============================================================
print("\n1. USING PRINT")
print("""
def calculate_order(items, tax_rate):
print("Starting calculate_order") # Debug print
total = sum(items)
print(f"Subtotal: {total}") # Debug print
tax = total * tax_rate
print(f"Tax: {tax}") # Debug print
final = total + tax
print(f"Final: {final}") # Debug print
return final
# Problems:
# - Print statements everywhere
# - Hard to turn off
# - No timestamp
# - No log levels
# - Can't filter by level
# - Can't easily write to files
""")
# ============================================================
# USING LOGGING - Better
# ============================================================
print("\n2. USING LOGGING")
print("""
import logging
logging.basicConfig(level=logging.INFO)
def calculate_order(items, tax_rate):
logging.info("Starting calculate_order")
total = sum(items)
logging.debug(f"Subtotal: {total}") # Won't show at INFO level
tax = total * tax_rate
logging.info(f"Tax: {tax}")
final = total + tax
logging.info(f"Final: {final}")
return final
# Benefits:
# - Log levels (DEBUG, INFO, WARNING, ERROR)
# - Easy to configure
# - Can write to files
# - Timestamps
# - Can be disabled without removing code
# - Different outputs (console, file, email)
""")
# ============================================================
# BENEFITS SUMMARY
# ============================================================
print("\n" + "-" * 30)
print("BENEFITS OF LOGGING")
print("-" * 30)
print("""
- Log levels for different severity
- Timestamps automatically added
- Can write to files, console, or network
- Can be configured without changing code
- Better than print for production code
- Can filter messages by level
- Thread-safe
- Can include stack traces for errors
""")
Benefits of logging:
- Log levels — control what gets logged
- Timestamps — know when events happened
- Multiple outputs — console, file, network
- Configurable — change without modifying code
- Better than print — more control and flexibility
Quick Check: Why is logging better than using print? (Answer: Logging has levels, timestamps, multiple outputs, and is configurable)
Log Levels
Different Levels for Different Situations
Python logging has five standard levels. Each level represents a different severity.
# Log Levels
print("=" * 50)
print("LOG LEVELS")
print("=" * 50)
# ============================================================
# FIVE STANDARD LEVELS
# ============================================================
print("\n1. FIVE STANDARD LEVELS")
print("""
┌─────────────┬─────────────────────────────────────────────────────┐
│ Level │ When to use │
├─────────────┼─────────────────────────────────────────────────────┤
│ DEBUG │ Detailed information for debugging │
│ INFO │ Confirmation that things are working as expected │
│ WARNING │ Something unexpected happened (still working) │
│ ERROR │ A problem occurred (function couldn't complete) │
│ CRITICAL │ A serious error (program might crash) │
└─────────────┴─────────────────────────────────────────────────────┘
""")
# ============================================================
# USING LOG LEVELS
# ============================================================
print("\n2. USING LOG LEVELS")
print("""
import logging
# Set the minimum level to show
logging.basicConfig(level=logging.INFO)
# Different log levels
logging.debug("Debug message - for development")
logging.info("Info message - normal operation")
logging.warning("Warning message - something unexpected")
logging.error("Error message - a problem occurred")
logging.critical("Critical message - serious error")
# With INFO level, only INFO and above are shown:
# INFO, WARNING, ERROR, CRITICAL
# DEBUG is hidden
""")
# ============================================================
# SETTING LOG LEVEL
# ============================================================
print("\n3. SETTING LOG LEVEL")
print("""
# Set to DEBUG - show everything
logging.basicConfig(level=logging.DEBUG)
# Set to ERROR - only show errors and critical
logging.basicConfig(level=logging.ERROR)
# Set to WARNING - show warnings and above
logging.basicConfig(level=logging.WARNING)
# In code:
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Set for a specific logger
""")
# ============================================================
# NUMERIC VALUES
# ============================================================
print("\n4. NUMERIC VALUES")
print("""
┌─────────────┬─────────┐
│ Level │ Value │
├─────────────┼─────────┤
│ DEBUG │ 10 │
│ INFO │ 20 │
│ WARNING │ 30 │
│ ERROR │ 40 │
│ CRITICAL │ 50 │
└─────────────┴─────────┘
Higher values = more severe
A level of 20 (INFO) shows messages with value >= 20
""")
Log levels key points:
- DEBUG — detailed info for debugging
- INFO — normal operation
- WARNING — something unexpected
- ERROR — problem occurred
- CRITICAL — serious error
Quick Check: Which log level is the most severe? (Answer: CRITICAL)
Basic Logging
Getting Started with Logging
The simplest way to use logging is with logging.basicConfig() and the module-level functions.
# Basic Logging
print("=" * 50)
print("BASIC LOGGING")
print("=" * 50)
# ============================================================
# SIMPLE LOGGING SETUP
# ============================================================
print("\n1. SIMPLE LOGGING SETUP")
print("""
import logging
# Basic configuration
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Log messages
logging.info("Application started")
logging.warning("Low disk space")
logging.error("Connection failed")
""")
# ============================================================
# LOGGING EXAMPLES
# ============================================================
print("\n2. LOGGING EXAMPLES")
import logging
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
print(" Logging configured with DEBUG level")
# Log messages
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
logging.error("This is an error message")
logging.critical("This is a critical message")
print(" Logs printed with timestamps and levels")
# ============================================================
# LOGGING CONFIGURATION OPTIONS
# ============================================================
print("\n3. LOGGING CONFIGURATION OPTIONS")
print("""
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename='app.log', # Write to file instead of console
filemode='w' # 'w' = overwrite, 'a' = append
)
# Without filename, logs go to console
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
""")
# ============================================================
# FORMAT ATTRIBUTES
# ============================================================
print("\n4. FORMAT ATTRIBUTES")
print("""
Common format attributes:
%(asctime)s - Time of the log message
%(levelname)s - Log level name (INFO, ERROR, etc.)
%(name)s - Logger name
%(message)s - The log message
%(filename)s - Source file name
%(lineno)d - Line number
%(funcName)s - Function name
%(pathname)s - Full path of source file
%(process)d - Process ID
%(thread)d - Thread ID
Example format:
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
'2024-01-15 10:30:45 - root - INFO - Application started'
""")
Basic logging key points:
- basicConfig() — configure logging once
- level — set minimum level to show
- format — customize log message format
- filename — write logs to a file
Quick Check: What function do you use to configure logging? (Answer: logging.basicConfig())
Advanced Logging
Handlers, Formatters, and Filters
Advanced logging lets you send logs to multiple places and customize the format.
# Advanced Logging
print("=" * 50)
print("ADVANCED LOGGING")
print("=" * 50)
# ============================================================
# LOGGERS, HANDLERS, FORMATTERS
# ============================================================
print("\n1. LOGGERS, HANDLERS, FORMATTERS")
print("""
Components of logging:
1. Logger - The object that creates log messages
2. Handler - Sends log messages to a destination (console, file, email)
3. Formatter - Defines the format of log messages
4. Filter - Controls which messages are logged
Example:
logger = logging.getLogger('my_app')
# Console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# File handler
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# Add handlers to logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
""")
# ============================================================
# MULTIPLE HANDLERS
# ============================================================
print("\n2. MULTIPLE HANDLERS")
print("""
import logging
# Create logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# Console handler
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# File handler
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
# Formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console.setFormatter(formatter)
file_handler.setFormatter(formatter)
# Add handlers
logger.addHandler(console)
logger.addHandler(file_handler)
# Now logs go to both console and file
logger.info("This goes to console and file")
logger.debug("This only goes to file (DEBUG is below INFO)")
""")
# ============================================================
# LOGGING EXCEPTIONS
# ============================================================
print("\n3. LOGGING EXCEPTIONS")
print("""
import logging
logging.basicConfig(level=logging.ERROR)
try:
result = 10 / 0
except ZeroDivisionError:
# Log the exception with full traceback
logging.exception("Division by zero occurred")
# Output:
# ERROR:root:Division by zero occurred
# Traceback (most recent call last):
# File "", line 2, in
# ZeroDivisionError: division by zero
""")
# ============================================================
# LOGGING EXCEPTIONS WITH CUSTOM MESSAGE
# ============================================================
print("\n4. LOGGING EXCEPTIONS WITH CUSTOM MESSAGE")
print("""
try:
result = 10 / 0
except ZeroDivisionError as e:
logging.error(f"Failed to calculate: {e}", exc_info=True)
# Same as logging.exception()
# With custom message:
try:
result = 10 / 0
except ZeroDivisionError:
logging.error("A division by zero error occurred", exc_info=True)
""")
# ============================================================
# LOGGER HIERARCHY
# ============================================================
print("\n5. LOGGER HIERARCHY")
print("""
import logging
# Root logger (used by logging.info(), etc.)
# Child loggers inherit from parent
# Create a logger with a name
logger = logging.getLogger(__name__) # '__main__' or module name
# Child logger
child_logger = logging.getLogger(__name__ + '.child')
# If parent has a handler, child inherits it
# But child can also have its own handlers
# Set levels separately
logger.setLevel(logging.INFO)
child_logger.setLevel(logging.DEBUG)
# Use the logger
logger.info("Parent logger")
child_logger.debug("Child logger - debug")
""")
# ============================================================
# SUMMARY
# ============================================================
print("\n6. SUMMARY")
print("""
┌─────────────────────┬────────────────────────────────────────────┐
│ Component │ Purpose │
├─────────────────────┼────────────────────────────────────────────┤
│ Logger │ Creates log messages │
│ Handler │ Sends logs to destination │
│ Formatter │ Formats log messages │
│ Filter │ Filters which logs to include │
│ Level │ Controls severity threshold │
└─────────────────────┴────────────────────────────────────────────┘
""")
Advanced logging key points:
- Logger — creates log messages
- Handler — sends logs to destinations
- Formatter — formats log messages
- exception() — logs exceptions with traceback
Quick Check: What function logs an exception with traceback? (Answer: logging.exception())
Real-World Example
Building a Web Application Logger
# Real-World Example: Web Application Logger
import logging
from datetime import datetime
import os
print("=" * 60)
print("WEB APPLICATION LOGGER")
print("=" * 60)
# ============================================================
# LOGGER CONFIGURATION
# ============================================================
def setup_logger():
"""Set up logging for the application"""
# Create logs directory if it doesn't exist
if not os.path.exists('logs'):
os.makedirs('logs')
# Create logger
logger = logging.getLogger('webapp')
logger.setLevel(logging.DEBUG)
# Clear existing handlers
logger.handlers.clear()
# Format for console
console_format = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Format for file (more detailed)
file_format = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Console handler
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(console_format)
# File handler - all logs
file_all = logging.FileHandler('logs/app.log')
file_all.setLevel(logging.DEBUG)
file_all.setFormatter(file_format)
# File handler - errors only
file_error = logging.FileHandler('logs/errors.log')
file_error.setLevel(logging.ERROR)
file_error.setFormatter(file_format)
# Add handlers
logger.addHandler(console)
logger.addHandler(file_all)
logger.addHandler(file_error)
return logger
# ============================================================
# WEB APPLICATION SIMULATION
# ============================================================
class WebApp:
"""Simulate a web application with logging"""
def __init__(self):
self.logger = setup_logger()
self.logger.info("Application starting up...")
def handle_request(self, endpoint, user_id, data=None):
"""Handle a request with logging"""
self.logger.info(f"Request received: {endpoint} from user {user_id}")
try:
if endpoint == "/login":
result = self.login(user_id, data)
elif endpoint == "/profile":
result = self.get_profile(user_id)
elif endpoint == "/update":
result = self.update_profile(user_id, data)
else:
result = {"error": "Unknown endpoint"}
self.logger.info(f"Request completed: {endpoint} (status: {result.get('status', 'unknown')})")
return result
except Exception as e:
self.logger.error(f"Error processing request: {e}", exc_info=True)
return {"error": str(e)}
def login(self, user_id, data):
"""Login user"""
self.logger.debug(f"Login attempt for user {user_id}")
if not data or 'password' not in data:
self.logger.warning(f"Login failed: missing password for user {user_id}")
return {"status": "failed", "reason": "Missing password"}
if user_id == 1 and data.get('password') == 'secret':
self.logger.info(f"User {user_id} logged in successfully")
return {"status": "success", "user": {"id": user_id, "name": "Admin"}}
else:
self.logger.warning(f"Login failed: invalid credentials for user {user_id}")
return {"status": "failed", "reason": "Invalid credentials"}
def get_profile(self, user_id):
"""Get user profile"""
self.logger.debug(f"Fetching profile for user {user_id}")
if user_id == 1:
return {"status": "success", "user": {"id": 1, "name": "Admin", "email": "admin@example.com"}}
else:
self.logger.warning(f"Profile not found for user {user_id}")
return {"status": "failed", "reason": "User not found"}
def update_profile(self, user_id, data):
"""Update user profile"""
self.logger.debug(f"Updating profile for user {user_id}")
if not data:
self.logger.warning(f"Update failed: no data for user {user_id}")
return {"status": "failed", "reason": "No data provided"}
if user_id == 1:
self.logger.info(f"Profile updated for user {user_id}: {data}")
return {"status": "success", "message": "Profile updated"}
else:
self.logger.warning(f"Update failed: user {user_id} not authorized")
return {"status": "failed", "reason": "Not authorized"}
# ============================================================
# DEMONSTRATION
# ============================================================
print("\n1. CREATING APPLICATION")
app = WebApp()
print(" Application started with logging")
print("\n2. HANDLING REQUESTS")
print(" Login successful:")
result = app.handle_request("/login", 1, {"password": "secret"})
print(f" {result}")
print("\n Login failed:")
result = app.handle_request("/login", 2, {"password": "wrong"})
print(f" {result}")
print("\n Get profile:")
result = app.handle_request("/profile", 1)
print(f" {result}")
print("\n Update profile:")
result = app.handle_request("/update", 1, {"email": "new@example.com"})
print(f" {result}")
print("\n Unknown endpoint:")
result = app.handle_request("/unknown", 1)
print(f" {result}")
print("\n3. LOG FILES CREATED")
print(" - logs/app.log (all logs)")
print(" - logs/errors.log (errors only)")
print(" Check these files for complete logs")
print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("""
- Use different log levels for different severity
- Log both success and failure cases
- Log exceptions with traceback
- Use separate files for different log levels
- Include timestamps and context
- Log enough detail to debug issues
""")
Real-world example key points:
- Multiple handlers — console, all logs, errors only
- Different formats — console vs file
- Log levels — debug, info, warning, error
- Exception logging — log errors with traceback
- Log files — separate files for different purposes
Quick Check: Why would you use separate files for different log levels? (Answer: To separate normal logs from errors for easier troubleshooting)
Best Practices
Logging Best Practices
# Best Practices for Logging
print("=" * 60)
print("BEST PRACTICES FOR LOGGING")
print("=" * 60)
# ============================================================
# 1. USE THE RIGHT LOG LEVEL
# ============================================================
print("\n1. USE THE RIGHT LOG LEVEL")
print("""
# Good - appropriate levels
logger.debug("Processing item #123") # Development details
logger.info("User alice logged in") # Normal operations
logger.warning("Disk usage at 85%") # Something to watch
logger.error("Failed to save file: permission denied") # Error
logger.critical("Database connection lost") # Critical failure
# Bad - wrong levels
logger.info("This is a detailed debug message") # Should be DEBUG
logger.debug("Failed to connect to API") # Should be ERROR
logger.error("Starting application") # Should be INFO
""")
# ============================================================
# 2. INCLUDE CONTEXT
# ============================================================
print("\n2. INCLUDE CONTEXT")
print("""
# Good - includes context
logger.error(f"User {user_id} failed to update profile: {error}")
# Better - includes timestamp, function, line number via formatter
# Already handled by the formatter
# Bad - no context
logger.error("Something went wrong")
""")
# ============================================================
# 3. DON'T LOG SENSITIVE INFORMATION
# ============================================================
print("\n3. DON'T LOG SENSITIVE INFORMATION")
print("""
# Bad - logging passwords
logger.info(f"User {user} logged in with password {password}")
# Bad - logging credit cards
logger.info(f"Payment processed for card {card_number}")
# Good - log without sensitive data
logger.info(f"User {user} logged in")
logger.info(f"Payment processed for user {user}")
# Good - mask sensitive data
logger.debug(f"API request: {request_data} (password masked)")
""")
# ============================================================
# 4. USE LOGGER HIERARCHY
# ============================================================
print("\n4. USE LOGGER HIERARCHY")
print("""
# Good - use module-level loggers
logger = logging.getLogger(__name__) # 'myapp.module'
# Good - create a hierarchy
app_logger = logging.getLogger('myapp')
module_logger = logging.getLogger('myapp.module')
function_logger = logging.getLogger('myapp.module.function')
# This allows different levels for different parts
""")
# ============================================================
# 5. HANDLE EXCEPTIONS PROPERLY
# ============================================================
print("\n5. HANDLE EXCEPTIONS PROPERLY")
print("""
# Good - log the exception
try:
result = risky_operation()
except Exception as e:
logger.error(f"Operation failed: {e}", exc_info=True)
# Or use logger.exception()
# Bad - log without traceback
try:
result = risky_operation()
except Exception as e:
logger.error(f"Operation failed: {e}") # No traceback
# Bad - no logging at all
try:
result = risky_operation()
except Exception:
pass # Silent failure
""")
# ============================================================
# 6. CONFIGURE LOGGING ONCE
# ============================================================
print("\n6. CONFIGURE LOGGING ONCE")
print("""
# Good - configure at the start of the application
# main.py
import logging
import config
def main():
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("Application started")
# Bad - configure in multiple places
""")
# ============================================================
# 7. SUMMARY
# ============================================================
print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
- Use appropriate log levels
- Include context in log messages
- Don't log sensitive information
- Use logger hierarchy (__name__)
- Log exceptions with traceback
- Configure logging once
- Use different handlers for different needs
- Set different levels for different environments
- Keep logs structured and consistent
""")
Best practices summary:
- Right level — use appropriate severity
- Include context — add useful information
- Don't log secrets — passwords, credit cards
- Use hierarchy —
__name__for module names - Log exceptions — include traceback
Quick Check: Should you log passwords? (Answer: No, never log sensitive information)
Try It Yourself
Experiment with logging in the editor below.
LOGGING - PRACTICE
==================================================
1. BASIC LOGGING
Logging configured with INFO level
2024-01-15 10:30:45,123 - INFO - This is INFO
2024-01-15 10:30:45,124 - WARNING - This is WARNING
2024-01-15 10:30:45,124 - ERROR - This is ERROR
DEBUG messages are hidden because level=INFO
2. LOGGER WITH NAME
2024-01-15 10:30:45,125 - practice - DEBUG - Debug message from named logger
2024-01-15 10:30:45,125 - practice - INFO - Info message from named logger
2024-01-15 10:30:45,125 - practice - WARNING - Warning message from named logger
3. LOGGING EXCEPTIONS
ERROR:root:Division by zero error
Traceback (most recent call last):
File "
ZeroDivisionError: division by zero
Exception logged with traceback
You've Got It!
You now understand logging in Python. You know how to use log levels, configure logging, and write logs to files.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is logging in Python?
What's the difference between logging and print?
How do I write logs to a file?
logging.basicConfig(filename='app.log') to write to a file. For more control, create a FileHandler: handler = logging.FileHandler('app.log') and add it to your logger.
What are log levels?
How do I log exceptions with traceback?
logging.exception() inside an except block. This logs the error message along with the full traceback. You can also use logging.error(msg, exc_info=True).
What's the best practice for logging in modules?
logger = logging.getLogger(__name__) in each module. This creates a hierarchy of loggers based on the module names, making it easy to control logging levels for different parts of your application.
Where to Go From Here
Now that you understand logging, check out these related topics:
Code Optimization
Learn how to write efficient Python code.
Learn More →Debugging
Learn techniques for debugging Python code.
Learn More →Docstrings
Learn about documenting your code.
Learn More →