Skip to content

Decorators

PyLogShield provides two function decorators for automatic exception logging, call tracing, and return-value logging: log_exceptions and its shorthand trace.

Quick Reference

Python
from pylogshield import get_logger, log_exceptions, trace

logger = get_logger("my_app")

@log_exceptions(logger)
def fetch_data(url: str) -> dict:
    ...

@trace(logger)
async def process_item(item_id: int) -> dict:
    ...

Parameters

log_exceptions

Parameter Type Default Description
logger PyLogShield required Logger to emit records to
log_calls bool False Log function name, args, kwargs at DEBUG on entry
log_returns bool False Log return value at DEBUG on success
raise_exception bool True Re-raise caught exceptions after logging. Set to False to suppress.
mask bool False Apply sensitive-data masking to all log messages

trace

Shorthand for log_exceptions(logger, log_calls=True, log_returns=True). raise_exception is always True.

Parameter Type Default Description
logger PyLogShield required Logger to emit records to
mask bool False Apply sensitive-data masking to all log messages

Examples

Basic exception logging (sync)

Python
from pylogshield import get_logger, log_exceptions

logger = get_logger("my_app")

@log_exceptions(logger)
def fetch_user(user_id: int) -> dict:
    response = requests.get(f"/users/{user_id}")
    response.raise_for_status()
    return response.json()

On an unhandled exception the logger emits a single ERROR record with the exception message and full traceback attached, then re-raises.

Basic exception logging (async)

Python
@log_exceptions(logger)
async def fetch_user(user_id: int) -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get(f"/users/{user_id}")
        response.raise_for_status()
        return response.json()

Async functions are detected automatically via inspect.iscoroutinefunction at decoration time.

Full call/return tracing

Python
@log_exceptions(logger, log_calls=True, log_returns=True)
def calculate_discount(price: float, pct: float) -> float:
    return price * (1 - pct / 100)

With log_level="DEBUG" on the logger, this emits:

Text Only
DEBUG  Calling calculate_discount(args=(99.99, 20), kwargs={}) from app.py:42
DEBUG  calculate_discount returned: 79.992

trace shorthand

Python
@trace(logger)
def calculate_discount(price: float, pct: float) -> float:
    return price * (1 - pct / 100)

Identical to the example above — trace always enables log_calls and log_returns.

Masking sensitive arguments

Python
@log_exceptions(logger, log_calls=True, log_returns=True, mask=True)
def authenticate(username: str, password: str) -> str:
    """Returns an auth token."""
    return auth_service.get_token(username, password)

With mask=True, masking is applied at three points:

  1. Entry log_mask() is called on args and kwargs before they are serialised to a string. A call like authenticate("alice", password="s3cr3t") logs kwargs={'password': '***'} rather than the raw value.
  2. Return log — the return value is masked before logging, so a returned token string matching key: value or key=value patterns is redacted.
  3. Exception log — exception .args strings are masked; the traceback text (locals) is not redacted.
Text Only
# example output with log_level="DEBUG", mask=True
DEBUG  Calling authenticate(args=('alice',), kwargs={'password': '***'}) from ...
DEBUG  authenticate returned: ***

Pattern matching on return values

mask=True on return values applies the regex to the string representation of the result. Objects that do not contain a recognisable key: value or key=value pattern will not be redacted. For structured masking, return a dict from the function and let the _mask_mapping path handle it.

Suppressing exceptions

Python
@log_exceptions(logger, raise_exception=False)
def notify_webhook(url: str, payload: dict) -> bool:
    requests.post(url, json=payload).raise_for_status()
    return True

result = notify_webhook("https://hook.example.com", {})
# result is None if an exception occurred; True on success

Use raise_exception=False when a failed operation should not abort the calling code — the exception is still logged at ERROR level.


API Reference

decorators

FUNCTION DESCRIPTION
log_exceptions

Wrap a function to log exceptions, and optionally calls and return values.

trace

Shorthand decorator for full entry/exit/exception tracing.

log_exceptions(logger: PyLogShield, log_calls: bool = False, log_returns: bool = False, raise_exception: bool = True, mask: bool = False) -> Callable[[Callable[P, R]], Callable[P, R]]

Wrap a function to log exceptions, and optionally calls and return values.

PARAMETER DESCRIPTION

logger

The logger to emit records to.

TYPE: PyLogShield

log_calls

If True, log function name, args, and kwargs at DEBUG level on entry.

TYPE: bool DEFAULT: False

log_returns

If True, log the return value at DEBUG level on success.

TYPE: bool DEFAULT: False

raise_exception

If True (default), re-raise caught exceptions after logging. If False, suppress the exception and return None.

TYPE: bool DEFAULT: True

mask

If True, apply PyLogShield sensitive-data masking to all log messages. Note: masking matches key: value / key=value patterns only -- raw dict repr in kwargs is not masked.

TYPE: bool DEFAULT: False

Source code in src/pylogshield/decorators.py
Python
def log_exceptions(
    logger: PyLogShield,
    log_calls: bool = False,
    log_returns: bool = False,
    raise_exception: bool = True,
    mask: bool = False,
) -> Callable[[Callable[P, R]], Callable[P, R]]:
    """Wrap a function to log exceptions, and optionally calls and return values.

    Parameters
    ----------
    logger : PyLogShield
        The logger to emit records to.
    log_calls : bool
        If True, log function name, args, and kwargs at DEBUG level on entry.
    log_returns : bool
        If True, log the return value at DEBUG level on success.
    raise_exception : bool
        If True (default), re-raise caught exceptions after logging.
        If False, suppress the exception and return None.
    mask : bool
        If True, apply PyLogShield sensitive-data masking to all log messages.
        Note: masking matches ``key: value`` / ``key=value`` patterns only --
        raw dict repr in ``kwargs`` is not masked.
    """

    def decorator(func):  # type: ignore[return]
        caller_info = _get_caller_info(func)

        if inspect.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args: P.args, **kwargs: P.kwargs):
                if log_calls:
                    _log_calls(logger, args, kwargs, caller_info, mask=mask)

                try:
                    result = await func(*args, **kwargs)

                    if log_returns:
                        _log_returns(
                            logger, caller_info["function_name"], result, mask=mask
                        )

                    return result

                except Exception as e:
                    logger.exception(
                        f"Exception in {caller_info['function_name']} "
                        f"(called from {caller_info['filename']}"
                        f":{caller_info['line_number']}): {e}",
                        mask=mask,
                    )

                    if raise_exception:
                        raise

                    return None

            return cast(Callable[P, Awaitable[R]], async_wrapper)

        @functools.wraps(func)
        def sync_wrapper(*args: P.args, **kwargs: P.kwargs):
            if log_calls:
                _log_calls(logger, args, kwargs, caller_info, mask=mask)

            try:
                result = func(*args, **kwargs)

                if log_returns:
                    _log_returns(
                        logger, caller_info["function_name"], result, mask=mask
                    )

                return result

            except Exception as e:
                logger.exception(
                    f"Exception in {caller_info['function_name']} "
                    f"(called from {caller_info['filename']}"
                    f":{caller_info['line_number']}): {e}",
                    mask=mask,
                )

                if raise_exception:
                    raise

                return None

        return cast(Callable[P, R], sync_wrapper)

    return decorator

trace(logger: PyLogShield, mask: bool = False) -> Callable[[Callable[P, R]], Callable[P, R]]

Shorthand decorator for full entry/exit/exception tracing.

Equivalent to log_exceptions(logger, log_calls=True, log_returns=True). raise_exception is always True.

PARAMETER DESCRIPTION

logger

The logger to emit records to.

TYPE: PyLogShield

mask

If True, apply sensitive-data masking to all log messages.

TYPE: bool DEFAULT: False

Source code in src/pylogshield/decorators.py
Python
def trace(
    logger: PyLogShield,
    mask: bool = False,
) -> Callable[[Callable[P, R]], Callable[P, R]]:
    """Shorthand decorator for full entry/exit/exception tracing.

    Equivalent to ``log_exceptions(logger, log_calls=True, log_returns=True)``.
    ``raise_exception`` is always ``True``.

    Parameters
    ----------
    logger : PyLogShield
        The logger to emit records to.
    mask : bool
        If True, apply sensitive-data masking to all log messages.
    """
    return log_exceptions(logger, log_calls=True, log_returns=True, mask=mask)