Raise Custom Exceptions and Log Errors Like a Pro

In production systems, silent failures or vague error messages can cause major downstream issues. Instead of just printing errors, we can log them for diagnostics and raise custom exceptions that provide clear, context-aware feedback to higher-level code — e.g., an API response handler.


Code Example

import math
import logging

# Configure logging to file
logging.basicConfig(filename='errors.log', level=logging.ERROR,
                    format='%(asctime)s - %(levelname)s - %(message)s')

# Define a custom exception
class InvalidLogInputError(Exception):
    def __init__(self, message):
        super().__init__(message)

def safe_log(x):
    if not isinstance(x, (int, float)):
        message = f"Invalid type: expected int or float, got {type(x).__name__}"
        logging.error(message)
        raise TypeError(message)

    if x <= 0:
        message = f"Invalid value: logarithm undefined for {x}"
        logging.error(message)
        raise InvalidLogInputError(message)

    try:
        return math.log(x)
    except ValueError as ve:
        logging.error(f"Unexpected math domain error: {ve}")
        raise InvalidLogInputError("Unexpected math domain error.") from ve

# --- Usage Example ---

# Valid input
try:
    print("log(10) =", safe_log(10))
except Exception as e:
    print(f"Handled error: {e}")

# Invalid input (zero)
try:
    print("log(0) =", safe_log(0))
except Exception as e:
    print(f"Handled error: {e}")

# Invalid input (string)
try:
    print("log('abc') =", safe_log("abc"))
except Exception as e:
    print(f"Handled error: {e}")

Explanation

Custom Exception (InvalidLogInputError)
  • Inherits from Exception, can be caught specifically by API consumers or calling code
  • Makes your errors semantic and domain-specific, which is better than using generic ValueError in large applications
Logging
  • Errors are logged to errors.log with timestamps, which is essential for postmortem debugging
  • logging.error() is used instead of print() — a best practice for scalable apps
Raising vs Returning
  • In real-world systems, especially web frameworks (e.g., Flask, FastAPI), you’d catch this custom exception at a higher layer and convert it into an HTTP 400/422 error with a clear message