Ojasa Mirai

Ojasa Mirai

Python

Loading...

Learning Level

🟢 Beginner🔵 Advanced
Exceptions OverviewException TypesTry-Except BlocksRaising ExceptionsCustom ExceptionsMultiple ExceptionsFinally & CleanupDebugging TechniquesLogging Best Practices
Python/Error Handling/Logging Best Practices

📝 Logging Best Practices — Professional Error Tracking

Learn to use Python's logging module instead of print statements for production-ready error tracking.


🎯 Why Logging Over Print?

Print statements have problems:

# ❌ Problems with print
print("User login attempted")  # Always shows, no way to turn off
print("ERROR: Database connection failed")  # No timestamp
print("Debug info:", user_data)  # Pollutes output with debug info

# ✅ Better: use logging
import logging

logging.info("User login attempted")
logging.error("Database connection failed")
logging.debug("Debug info: %s", user_data)  # Can disable debug messages

Logging advantages:

  • Turn messages on/off by severity level
  • Automatic timestamps and formatting
  • Write to files instead of console
  • Customize output for different purposes
  • Works in production environments

🔧 Basic Logging Setup

import logging

# Simple setup
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Now use logging
logging.debug("Detailed debug information")
logging.info("General informational messages")
logging.warning("Warning messages for potential issues")
logging.error("Error messages for serious problems")
logging.critical("Critical messages for emergency situations")

# Output:
# 2024-02-23 10:30:45,123 - root - DEBUG - Detailed debug information
# 2024-02-23 10:30:45,124 - root - INFO - General informational messages
# 2024-02-23 10:30:45,125 - root - WARNING - Warning messages for potential issues
# 2024-02-23 10:30:45,126 - root - ERROR - Error messages for serious problems
# 2024-02-23 10:30:45,127 - root - CRITICAL - Critical messages for emergency situations

📊 Log Levels Explained

LevelWhen to UseExample
DEBUGDevelopment detailsVariable values, loop iterations
INFOImportant events"User logged in", "File processed"
WARNINGPotential issues"API rate limit approaching"
ERRORSomething failed"Connection timeout", "Invalid input"
CRITICALSystem failure"Out of disk space", "Database down"
import logging

# Different severity levels
def process_user_registration(username, email):
    logging.debug(f"Processing registration: username={username}")

    if not validate_email(email):
        logging.warning(f"Invalid email format: {email}")
        return False

    try:
        save_to_database(username, email)
        logging.info(f"User registered successfully: {username}")
        return True
    except DatabaseError as e:
        logging.error(f"Database error during registration: {e}")
        return False
    except Exception as e:
        logging.critical(f"Unexpected error during registration: {e}")
        raise

📂 Logging to Files

import logging

# Log to both console and file
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('app.log'),      # File handler
        logging.StreamHandler()               # Console handler
    ]
)

logging.info("This goes to both file and console")

# Only important messages to console, everything to file
import logging

# Setup logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# File handler (gets all messages)
file_handler = logging.FileHandler('debug.log')
file_handler.setLevel(logging.DEBUG)

# Console handler (only warnings and above)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)

# Format
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# Add handlers
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# Now use
logger.debug("Debug info (only in file)")
logger.warning("Warning (console and file)")
logger.error("Error (console and file)")

🏗️ Professional Logging Structure

import logging
import sys

# Create logger for your module
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# Create handlers
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)

file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)

# Create formatter
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# Add handlers
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# Now use in your code
def fetch_api_data(endpoint):
    logger.debug(f"Fetching from endpoint: {endpoint}")

    try:
        response = requests.get(endpoint, timeout=5)
        logger.info(f"Successfully fetched data from {endpoint}")
        return response.json()
    except requests.Timeout:
        logger.warning(f"Request timeout for {endpoint}")
        return None
    except requests.ConnectionError as e:
        logger.error(f"Connection error: {e}")
        return None
    except Exception as e:
        logger.critical(f"Unexpected error: {e}")
        raise

🎨 Practical Logging Examples

import logging

logger = logging.getLogger(__name__)

# Example 1: File operations with logging
def read_config_file(filename):
    logger.info(f"Reading configuration from {filename}")

    try:
        with open(filename) as f:
            config = json.load(f)
        logger.debug(f"Successfully loaded config with {len(config)} keys")
        return config

    except FileNotFoundError:
        logger.error(f"Configuration file not found: {filename}")
        logger.info("Using default configuration")
        return {}

    except json.JSONDecodeError as e:
        logger.error(f"Invalid JSON in {filename}: {e}")
        return {}

# Example 2: Database operations with logging
class UserDatabase:
    def __init__(self, connection_string):
        self.connection_string = connection_string
        logger.info(f"Initializing database: {connection_string}")

    def get_user(self, user_id):
        logger.debug(f"Fetching user {user_id}")

        try:
            # Simulate database query
            user = self._query(f"SELECT * FROM users WHERE id = {user_id}")
            logger.debug(f"Found user: {user['name']}")
            return user

        except DatabaseError as e:
            logger.error(f"Database error fetching user {user_id}: {e}")
            raise

        except Exception as e:
            logger.critical(f"Unexpected error fetching user {user_id}: {e}")
            raise

    def _query(self, query):
        pass

# Example 3: API request logging
def call_external_api(endpoint, params):
    logger.info(f"Calling external API: {endpoint}")
    logger.debug(f"Parameters: {params}")

    try:
        response = requests.post(endpoint, json=params, timeout=5)
        response.raise_for_status()

        logger.info(f"API call successful: {endpoint}")
        logger.debug(f"Response: {response.text[:200]}")  # Log first 200 chars
        return response.json()

    except requests.Timeout:
        logger.warning(f"API timeout: {endpoint}")
        return None

    except requests.HTTPError as e:
        logger.error(f"API error {response.status_code}: {e}")
        return None

    except Exception as e:
        logger.critical(f"Unexpected API error: {e}")
        raise

🔍 Advanced Logging Features

import logging

# 1. Include exception info
logger = logging.getLogger(__name__)

def risky_operation():
    try:
        result = 10 / 0
    except ZeroDivisionError:
        # Log with exception info
        logger.error("Division by zero", exc_info=True)
        # Output includes full traceback!

# 2. Variable substitution (safer than f-strings for logging)
logger.info("Processing file: %s with size %d bytes", filename, file_size)

# 3. Multiple loggers for different modules
api_logger = logging.getLogger('myapp.api')
db_logger = logging.getLogger('myapp.database')
auth_logger = logging.getLogger('myapp.auth')

# Each can have different configuration

# 4. Logger hierarchy
root_logger = logging.getLogger()
app_logger = logging.getLogger('myapp')
auth_logger = logging.getLogger('myapp.auth')
# auth_logger inherits from app_logger which inherits from root_logger

⚠️ Common Logging Mistakes

# ❌ Mistake 1: Logging personal data
logger.info(f"User created: {email}, password: {password}")

# ✅ Better: don't log sensitive data
logger.info(f"User created: {email}")

# ❌ Mistake 2: Using print in production
print("Error occurred!")  # Hard to control

# ✅ Better: use logging
logger.error("Error occurred!")  # Can disable or redirect

# ❌ Mistake 3: Not setting up logging properly
# Just using logging.info() without configuration

# ✅ Better: configure at application start
if __name__ == '__main__':
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('app.log'),
            logging.StreamHandler()
        ]
    )
    main()

# ❌ Mistake 4: Logging too much
for item in millions_of_items:
    logger.debug(f"Processing {item}")  # Creates massive log file!

# ✅ Better: log summaries
logger.info(f"Processing {len(items)} items")
# Then log details only for errors

🎯 Logging Configuration Best Practices

import logging
import logging.config

# Use a config dictionary for complex setups
LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
        'detailed': {
            'format': '%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(funcName)s() - %(message)s'
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'standard',
            'stream': 'ext://sys.stdout'
        },
        'file': {
            'class': 'logging.FileHandler',
            'level': 'DEBUG',
            'formatter': 'detailed',
            'filename': 'debug.log'
        },
    },
    'loggers': {
        '': {
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
        }
    }
}

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)

# Now all loggers are configured consistently
logger.info("Application started")

🎯 Key Takeaways

  • ✅ Use logging instead of print statements
  • ✅ Configure logging at application startup
  • ✅ Use appropriate log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL)
  • ✅ Log to files for production applications
  • ✅ Don't log sensitive information (passwords, API keys)
  • ✅ Use formatting for consistent, readable output
  • ✅ Include context (user ID, operation, etc.) in messages

Completed: Error Handling Fundamentals


Resources

Python Docs

Ojasa Mirai

Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.

Learn Deep • Build Real • Verify Skills • Launch Forward

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn