Skip to main content

An exception wrapper for decorating functions with detailed generic error handling and logging.

Project description

cdlogo

Exception Logger

Rather than needing to remember all of the exception types or do "catch all" exceptions, and rather than having to reimplement logging formats constantly, this module provides consistent and comprehensive logging and error handing approach.

Features

  • Decorator-based exception handling: Simply add @exception_handler or @exception_handler_quiet to any function
  • Comprehensive coverage: Handles all standard Python built-in exceptions
  • Structured logging: ISO timestamps, UUIDs, function names, and error details
  • Exception correlation: Track related errors across multiple function calls with shared UUIDs/IDs/strings
  • Automatic re-raising: Logs exceptions and re-raises them for proper error propagation OR _quiet which does not re-raise
  • Manual logging support: For legacy code that can't use decorators

Log format

Each logged exception follows this structure:

ISO-8601-Timestamp - UUIDv4 (or whatever you want) - function_name - [logged args: key1: value1, key2: value2] - ERROR: ExceptionType: message (File: filename.py, Line: line_number)

Example without logged arguments:

2026-02-08T08:14:36.013748+00:00 - 4b1274be-0428-4d11-ae0d-6402542ba7f6 - process_data - ERROR: KeyError: 'required_field' (File: app.py, Line: 42)

Example with logged arguments:

2026-02-08T08:13:51.620325+00:00 - f011e24d-0ae3-4982-8cf5-db39c056c905 - process_api_request - logged args: ip_address: 192.168.1.100, request_id: req_abc123, user_id: 12345 - ERROR: KeyError: 'required_field' (File: api_handler.py, Line: 95)

Instead of a UUIDv4, we could put something else there, such as the name of the service:

2026-02-08T08:13:51.620325+00:00 - web_correlator_green - process_api_request - logged args: ip_address: 192.168.1.100, request_id: req_abc123, user_id: 12345 - ERROR: KeyError: 'required_field' (File: api_handler.py, Line: 95)

Installation

Install with pip or uv:

uv add exception_logger
pip install exception_logger

Or include the module as exception_logger.py in your project, along with the MIT license.

Usage

The decorator automatically:

  • Accepts a UUID (or whatever string you like) for logging or generates a UUIDv4 if one isn't provided
  • Detects the function name and logs it
  • Logs any exception with full details
  • Adds specified custom args to log lines
  • Re-raises the exception (unless the 'quiet' version is used)

There are two different functions available to decorate with, exception_handler and exception_handler_quiet.

The _quiet version suppresses most tracebacks and does not re-raise the error. The only type of traceback that won't be surpressed are tracebacks outside of the fucnction that is decorated. So even if you decorate 'main', a syntax error that makes the file invalid Python will still have a traceback.

Use caution with the _quiet version, as no further error handling is done in that case!

Typically we want to use exception_handler instead of exception_handler_quiet so that the error is re-raised, but the quiet version is useful in cases where the function being decorated is the top level, such as when decorating main in the final script.

The quiet version can be nice when we want to keep our log files very homogenous and single line, and completely in STDOUT. Of course we can still keep log files nice with the regular version too, with a little bit of work to handle our raised exceptions appropriately, like normal.

Slap the decorator on main

First let's look at a brutishly simple approach that logs all errors in the whole program and removes tracebacks from main and everything main calls.

from exception_logger import exception_handler_quiet

@exception_handler_quiet
def main():
    # do everything normally and reap the benfits of the exception logging!

If you decorate the main function, then there normally wouldn't be a need to decorate specific functions as that could duplicate error logs.

While decorating main is super easy and actually awesome in some cases, in many cases you might want to customize the usage further and decorate individual functions.

Using the _quiet version on main (or any function) can be dangerous since errors won't propagate any further.

Decorating a function

from exception_logger import exception_handler

@exception_handler
def risky_operation(data):
    # Your code here
    return data['key']  # May raise KeyError

Custom exception id and function name

Pass custom values for correlation across multiple operations:

@exception_handler
def database_query(query, exception_id=None, func_name=None):
    # Your code here

# Call with custom tracking
transaction_id = str(uuid.uuid4())
database_query("SELECT * FROM users", exception_id=transaction_id)

Contextual logging with log_this_* arguments

Automatically include important context in error logs by using log_this_* prefixed arguments. These are automatically extracted, logged, and removed before your function executes:

@exception_handler
def process_payment(
    amount,
    user_id,
    exception_id=None,
    func_name=None,
    log_this_user_id=None,
    log_this_transaction_id=None,
    log_this_amount=None,
    log_this_currency=None
):
    # Your business logic here
    if amount > 10000:
        raise ValueError("Amount exceeds limit")
    return process_charge(amount)

# Call with context
process_payment(
    amount=5000,
    user_id=12345,
    log_this_user_id=12345,
    log_this_transaction_id="txn_abc123",
    log_this_amount=5000,
    log_this_currency="USD"
)

If an exception occurs, the log will include:

2026-02-08T07:13:50.984742+00:00 - 15babb32-2b96-430a-8914-c7b15d2b955a - process_payment - logged args: amount: 5000, currency: USD, transaction_id: txn_abc123, user_id: 12345 - ERROR: ValueError: Amount exceeds limit (Line: 42)

Benefits:

  • Automatically includes relevant context without manual string formatting
  • Self-documenting - function signature shows what gets logged
  • Searchable logs - easily find errors by user_id, transaction_id, etc.
  • Clean code - no log message construction cluttering your business logic
  • Type-safe - works with IDE autocomplete

Manual exception handling

For legacy code or special cases, you can manually log exceptions with context:

from exception_logger import handle_exception
import uuid

def legacy_function(user_id, transaction_id):
    operation_id = str(uuid.uuid4())
    try:
        # Risky operation
        result = some_operation()
    except Exception:
        handle_exception(
            exception_id=operation_id,
            func_name="legacy_function",
            user_id=user_id,
            transaction_id=transaction_id,
            operation="payment_processing"
        )
        raise

Note: When using handle_exception(), you can pass any keyword arguments as context (not just log_this_* prefixed ones).

Advanced Usage

Correlated exception tracking

Track multiple related operations with a shared UUID:

@exception_handler
def step_1(data, exception_id=None, func_name=None):
    return data.process()

@exception_handler
def step_2(data, exception_id=None, func_name=None):
    return data.validate()

def workflow(input_data):
    # Use same UUID for entire workflow
    workflow_id = str(uuid.uuid4())

    result1 = step_1(input_data, exception_id=workflow_id)
    result2 = step_2(result1, exception_id=workflow_id)

    return result2

All exceptions in this workflow will share the same workflow_id, making it easy to track related errors in logs.

Nested function calls

Each function maintains its own exception handling:

@exception_handler
def parent_function(data, exception_id=None, func_name=None):
    return child_function(data)

@exception_handler
def child_function(data, exception_id=None, func_name=None):
    return data['key']  # May raise KeyError

Covered exception types

The module handles all standard Python built-in exceptions, including:

System exceptions

  • KeyboardInterrupt
  • SystemExit
  • SystemError

OS and I/O exceptions

  • FileNotFoundError
  • FileExistsError
  • PermissionError
  • IsADirectoryError
  • NotADirectoryError
  • TimeoutError
  • ConnectionError and subclasses
  • OSError and all subclasses

Arithmetic exceptions

  • ZeroDivisionError
  • FloatingPointError
  • OverflowError
  • ArithmeticError

Type and value exceptions

  • TypeError
  • ValueError
  • UnicodeError and subclasses
    • UnicodeDecodeError
    • UnicodeEncodeError
    • UnicodeTranslateError

Lookup exceptions

  • KeyError
  • IndexError
  • AttributeError
  • NameError
  • UnboundLocalError
  • LookupError

Import exceptions

  • ModuleNotFoundError
  • ImportError

Memory and resource exceptions

  • MemoryError
  • RecursionError

Runtime exceptions

  • NotImplementedError
  • StopIteration
  • StopAsyncIteration
  • GeneratorExit
  • RuntimeError
  • RuntimeWarning

Syntax exceptions

  • SyntaxError
  • IndentationError
  • TabError

Other exceptions

  • AssertionError
  • BufferError
  • EOFError
  • ReferenceError
  • Exception (base class)
  • BaseException (ultimate base)

More examples

API request handler

@exception_handler
def handle_api_request(
    request_data,
    exception_id=None,
    func_name=None,
    log_this_user_id=None,
    log_this_request_id=None,
    log_this_endpoint=None,
    log_this_method=None,
    log_this_ip_address=None
):
    """Handle API request with full context logging."""
    result = validate_and_process(request_data)
    return result

# Usage
handle_api_request(
    request_data={"action": "update_profile"},
    log_this_user_id=12345,
    log_this_request_id="req_abc123",
    log_this_endpoint="/api/v1/users/profile",
    log_this_method="POST",
    log_this_ip_address="192.168.1.100"
)

Database transaction

@exception_handler
def execute_transaction(
    query,
    params,
    exception_id=None,
    func_name=None,
    log_this_transaction_id=None,
    log_this_table=None,
    log_this_operation=None,
    log_this_user_id=None
):
    """Execute database transaction with context."""
    return db.execute(query, params)

# Usage with shared transaction ID
txn_id = str(uuid.uuid4())
execute_transaction(
    "UPDATE users SET status = ? WHERE id = ?",
    ["active", 123],
    exception_id=txn_id,
    log_this_transaction_id=txn_id,
    log_this_table="users",
    log_this_operation="UPDATE",
    log_this_user_id=123
)

ML model serving

@exception_handler
def predict(
    model_input,
    exception_id=None,
    func_name=None,
    log_this_model_name=None,
    log_this_model_version=None,
    log_this_request_id=None,
    log_this_batch_size=None
):
    """Run model inference with tracking."""
    predictions = model.predict(model_input)
    return predictions

# Usage
predict(
    model_input={"features": [1, 2, 3]},
    log_this_model_name="fraud_detector",
    log_this_model_version="v2.1.0",
    log_this_request_id="pred_xyz789",
    log_this_batch_size=1
)

Best practices

  1. Use log_this_* for important context: Include relevant business context like user IDs, transaction IDs, request IDs, etc. This makes debugging much easier.

  2. Use consistent exception_id for related operations: This helps trace errors across distributed systems or complex workflows.

  3. Let exceptions propagate: The decorator re-raises all exceptions after logging, ensuring proper error handling up the call stack.

  4. Use descriptive function names: Since the function name appears in logs, clear names help with debugging.

  5. Monitor stdout: All logs are written to stdout.

  6. Parse logs programmatically: The structured format makes it easy to parse logs for monitoring and alerting systems.

  7. Don't overload with log_this_ arguments*: Include only the most relevant context. Too many fields can make logs hard to read.

  8. Use consistent naming: If you log user_id in one function, use the same name in others for easier log searching.

  9. Only use the quiet version if the error doesn't need to be raised any further.: Use the exception_handler_quiet on the top-level of error handling.

Example Log Output

Without context arguments

2026-02-08T08:14:36.013748+00:00 - 4b1274be-0428-4d11-ae0d-6402542ba7f6 - test_division_by_zero - ERROR: ZeroDivisionError: division by zero (File: test.py, Line: 14)
2026-02-08T08:14:36.013911+00:00 - 46cd01e0-fdf2-4186-bc6c-f4930627b58e - test_file_not_found - ERROR: FileNotFoundError: [Errno 2] No such file or directory: '/nonexistent/file.txt' (File: test.py, Line: 22)
2026-02-08T08:14:36.014010+00:00 - 76ed8968-e799-4b84-afa9-3a75a80ea120 - test_key_error - ERROR: KeyError: 'b' (File: test.py, Line: 30)

With context arguments (log_this_*)

2026-02-08T08:13:51.620325+00:00 - f011e24d-0ae3-4982-8cf5-db39c056c905 - process_api_request - logged args: ip_address: 192.168.1.100, request_id: req_abc123, user_id: 12345 - ERROR: KeyError: 'required_field' (File: log_this_examples.py, Line: 30)
2026-02-08T08:13:51.620590+00:00 - 59f947e5-fbf4-4450-a797-19b82f7467e6 - process_payment - logged args: amount: 15000, currency: USD, merchant_id: merch_999, payment_method: credit_card, user_id: 54321 - ERROR: ValueError: Amount exceeds limit: 15000 (File: log_this_examples.py, Line: 69)
2026-02-08T08:13:51.620759+00:00 - 3ff961d3-6952-4c74-8312-fbfbc6135917 - process_uploaded_file - logged args: file_size: 2048576, filename: document.pdf, mime_type: application/pdf, uploader_id: 88888 - ERROR: FileNotFoundError: [Errno 2] No such file or directory: '/nonexistent/file.txt' (File: log_this_examples.py, Line: 85)

Requirements

  • Python 3.6+
  • No external dependencies (uses only standard library)

Project information

This project is usees the MIT License.

This project is reviewed and maintained by a human.

Feel free to contribute with Issues or PRs on Github. The project is maintained as best as is reasonable.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

exception_logger-0.1.2.tar.gz (10.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

exception_logger-0.1.2-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file exception_logger-0.1.2.tar.gz.

File metadata

  • Download URL: exception_logger-0.1.2.tar.gz
  • Upload date:
  • Size: 10.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.7

File hashes

Hashes for exception_logger-0.1.2.tar.gz
Algorithm Hash digest
SHA256 91fe142f63529d89ed69b8367cfddbcc66bc50c8f2230434ba34d7efbd047c6b
MD5 762be0d07afdaf5983bb3485afe2483f
BLAKE2b-256 c9d58df40c0660ef904e0422c34c5941ecb13a14251c7381876f445f7107a685

See more details on using hashes here.

File details

Details for the file exception_logger-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for exception_logger-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1f1ad9377e3652ff279059b640cb6b5226113c7361e1aa085291414b738d2775
MD5 67126959fa469073b9709e7bdd1b0c72
BLAKE2b-256 76c1bf12f6d62bdfc4f3176ecb3748de678a7c967e812ce0a18497072bf2ad42

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page