Skip to content

logging

ColourFormatter

Bases: Formatter

Custom logging formatter that adds color to log level names.

This formatter applies color coding to log level names in the console output to improve readability and visual distinction between different log levels.

Source code in src/holmes/logging.py
class ColourFormatter(logging.Formatter):  # pragma: no cover
    """
    Custom logging formatter that adds color to log level names.

    This formatter applies color coding to log level names in the console
    output to improve readability and visual distinction between different
    log levels.
    """

    level_name_colours = {
        logging.DEBUG: lambda level: click.style(str(level), fg="cyan"),
        logging.INFO: lambda level: click.style(str(level), fg="green"),
        logging.WARNING: lambda level: click.style(str(level), fg="yellow"),
        logging.ERROR: lambda level: click.style(str(level), fg="red"),
        logging.CRITICAL: lambda level: click.style(
            str(level), fg="bright_red"
        ),
    }

    def __init__(
        self: "ColourFormatter",
        fmt: Optional[str] = None,
        datefmt: Optional[str] = None,
        style: Literal["%", "{", "$"] = "%",
        use_colours: Optional[bool] = None,
    ) -> None:
        """
        Initialize the ColourFormatter.

        Parameters
        ----------
        fmt : Optional[str], default=None
            Format string for log messages
        datefmt : Optional[str], default=None
            Format string for dates
        style : Literal["%", "{", "$"], default="%"
            Style of the format string
        use_colours : Optional[bool], default=None
            Whether to use colors. If None, colors are used if stdout is a TTY
        """
        if use_colours in (True, False):
            self.use_colours = use_colours  # pragma: no cover
        else:
            self.use_colours = sys.stdout.isatty()
        super().__init__(fmt=fmt, datefmt=datefmt, style=style)

    def colour_level_name(
        self: "ColourFormatter", level_name: str, level_no: int
    ) -> str:
        """
        Apply color to a log level name based on its severity.

        Parameters
        ----------
        level_name : str
            Name of the log level to color
        level_no : int
            Numeric value of the log level

        Returns
        -------
        str
            Colored log level name
        """
        fct = self.level_name_colours.get(
            level_no,
            lambda level_name: str(  # pylint: disable=unnecessary-lambda
                level_name
            ),
        )  # pragma: no cover
        return fct(level_name)  # pragma: no cover

    def formatMessage(
        self: "ColourFormatter", record: logging.LogRecord
    ) -> str:
        """
        Format a log record with colored level name if colors are enabled.

        Parameters
        ----------
        record : logging.LogRecord
            Log record to format

        Returns
        -------
        str
            Formatted log message
        """
        if self.use_colours:
            record.levelname = self.colour_level_name(
                record.levelname, record.levelno
            )  # pragma: no cover
        return super().formatMessage(record)

__init__

__init__(
    fmt: Optional[str] = None,
    datefmt: Optional[str] = None,
    style: Literal["%", "{", "$"] = "%",
    use_colours: Optional[bool] = None,
) -> None

Initialize the ColourFormatter.

Parameters:

Name Type Description Default
fmt Optional[str]

Format string for log messages

None
datefmt Optional[str]

Format string for dates

None
style Literal['%', '{', '$']

Style of the format string

"%"
use_colours Optional[bool]

Whether to use colors. If None, colors are used if stdout is a TTY

None
Source code in src/holmes/logging.py
def __init__(
    self: "ColourFormatter",
    fmt: Optional[str] = None,
    datefmt: Optional[str] = None,
    style: Literal["%", "{", "$"] = "%",
    use_colours: Optional[bool] = None,
) -> None:
    """
    Initialize the ColourFormatter.

    Parameters
    ----------
    fmt : Optional[str], default=None
        Format string for log messages
    datefmt : Optional[str], default=None
        Format string for dates
    style : Literal["%", "{", "$"], default="%"
        Style of the format string
    use_colours : Optional[bool], default=None
        Whether to use colors. If None, colors are used if stdout is a TTY
    """
    if use_colours in (True, False):
        self.use_colours = use_colours  # pragma: no cover
    else:
        self.use_colours = sys.stdout.isatty()
    super().__init__(fmt=fmt, datefmt=datefmt, style=style)

colour_level_name

colour_level_name(level_name: str, level_no: int) -> str

Apply color to a log level name based on its severity.

Parameters:

Name Type Description Default
level_name str

Name of the log level to color

required
level_no int

Numeric value of the log level

required

Returns:

Type Description
str

Colored log level name

Source code in src/holmes/logging.py
def colour_level_name(
    self: "ColourFormatter", level_name: str, level_no: int
) -> str:
    """
    Apply color to a log level name based on its severity.

    Parameters
    ----------
    level_name : str
        Name of the log level to color
    level_no : int
        Numeric value of the log level

    Returns
    -------
    str
        Colored log level name
    """
    fct = self.level_name_colours.get(
        level_no,
        lambda level_name: str(  # pylint: disable=unnecessary-lambda
            level_name
        ),
    )  # pragma: no cover
    return fct(level_name)  # pragma: no cover

formatMessage

formatMessage(record: LogRecord) -> str

Format a log record with colored level name if colors are enabled.

Parameters:

Name Type Description Default
record LogRecord

Log record to format

required

Returns:

Type Description
str

Formatted log message

Source code in src/holmes/logging.py
def formatMessage(
    self: "ColourFormatter", record: logging.LogRecord
) -> str:
    """
    Format a log record with colored level name if colors are enabled.

    Parameters
    ----------
    record : logging.LogRecord
        Log record to format

    Returns
    -------
    str
        Formatted log message
    """
    if self.use_colours:
        record.levelname = self.colour_level_name(
            record.levelname, record.levelno
        )  # pragma: no cover
    return super().formatMessage(record)

RouteFilter

Bases: Filter

Filter for removing common route access logs.

This filter prevents logging of successful (200) GET requests to common routes like the homepage, static files, and ping endpoint to reduce log noise.

Source code in src/holmes/logging.py
class RouteFilter(logging.Filter):
    """
    Filter for removing common route access logs.

    This filter prevents logging of successful (200) GET requests to common routes
    like the homepage, static files, and ping endpoint to reduce log noise.
    """

    def __init__(self: "RouteFilter", *args: str, **kwargs: str) -> None:
        """
        Initialize the RouteFilter.

        Parameters
        ----------
        *args : str
            Positional arguments passed to the parent class
        **kwargs : str
            Keyword arguments passed to the parent class
        """
        super().__init__(*args, **kwargs)

    def filter(self: "RouteFilter", record: logging.LogRecord) -> bool:
        """
        Filter log records based on common routes.

        This method filters out successful (200) GET requests to common routes
        to reduce log verbosity.

        Parameters
        ----------
        record : logging.LogRecord
            Log record to filter

        Returns
        -------
        bool
            True if the record should be logged, False if it should be filtered out
        """
        routes = [
            "/ping",
            "/health",
            "/",
            "/static/scripts/.+.js",
            "/static/styles/.+.css",
        ]
        msg = record.getMessage()
        return all(
            re.search(f'"GET {route}(?:\\?\\S+)? HTTP/1.1" 200', msg) is None
            for route in routes
        )

__init__

__init__(*args: str, **kwargs: str) -> None

Initialize the RouteFilter.

Parameters:

Name Type Description Default
*args str

Positional arguments passed to the parent class

()
**kwargs str

Keyword arguments passed to the parent class

{}
Source code in src/holmes/logging.py
def __init__(self: "RouteFilter", *args: str, **kwargs: str) -> None:
    """
    Initialize the RouteFilter.

    Parameters
    ----------
    *args : str
        Positional arguments passed to the parent class
    **kwargs : str
        Keyword arguments passed to the parent class
    """
    super().__init__(*args, **kwargs)

filter

filter(record: LogRecord) -> bool

Filter log records based on common routes.

This method filters out successful (200) GET requests to common routes to reduce log verbosity.

Parameters:

Name Type Description Default
record LogRecord

Log record to filter

required

Returns:

Type Description
bool

True if the record should be logged, False if it should be filtered out

Source code in src/holmes/logging.py
def filter(self: "RouteFilter", record: logging.LogRecord) -> bool:
    """
    Filter log records based on common routes.

    This method filters out successful (200) GET requests to common routes
    to reduce log verbosity.

    Parameters
    ----------
    record : logging.LogRecord
        Log record to filter

    Returns
    -------
    bool
        True if the record should be logged, False if it should be filtered out
    """
    routes = [
        "/ping",
        "/health",
        "/",
        "/static/scripts/.+.js",
        "/static/styles/.+.css",
    ]
    msg = record.getMessage()
    return all(
        re.search(f'"GET {route}(?:\\?\\S+)? HTTP/1.1" 200', msg) is None
        for route in routes
    )

get_correlation_id

get_correlation_id() -> str | None

Get the current correlation ID for request tracing.

Source code in src/holmes/logging.py
def get_correlation_id() -> str | None:
    """Get the current correlation ID for request tracing."""
    return _correlation_id.get()

set_correlation_id

set_correlation_id(correlation_id: str) -> None

Set the correlation ID for the current context.

Source code in src/holmes/logging.py
def set_correlation_id(correlation_id: str) -> None:
    """Set the correlation ID for the current context."""
    _correlation_id.set(correlation_id)

log_with_timing

log_with_timing(func: Callable[P, R]) -> Callable[P, R]

Decorator that logs function execution time.

P7-LOG-04: Performance monitoring via timing decorator.

Parameters:

Name Type Description Default
func Callable

Function to wrap with timing

required

Returns:

Type Description
Callable

Wrapped function that logs execution time

Source code in src/holmes/logging.py
def log_with_timing(
    func: Callable[P, R],
) -> Callable[P, R]:
    """
    Decorator that logs function execution time.

    P7-LOG-04: Performance monitoring via timing decorator.

    Parameters
    ----------
    func : Callable
        Function to wrap with timing

    Returns
    -------
    Callable
        Wrapped function that logs execution time
    """

    @functools.wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        start_time = time.perf_counter()
        try:
            result = func(*args, **kwargs)
            return result
        finally:
            elapsed = time.perf_counter() - start_time
            logger.debug(f"{func.__name__} completed in {elapsed:.3f}s")

    return wrapper

log_exception

log_exception(exc: Exception, message: str = 'An error occurred') -> None

Log an exception with full stack trace.

P7-LOG-01: Ensure exceptions are logged with stack traces.

Parameters:

Name Type Description Default
exc Exception

The exception to log

required
message str

Additional context message

'An error occurred'
Source code in src/holmes/logging.py
def log_exception(exc: Exception, message: str = "An error occurred") -> None:
    """
    Log an exception with full stack trace.

    P7-LOG-01: Ensure exceptions are logged with stack traces.

    Parameters
    ----------
    exc : Exception
        The exception to log
    message : str
        Additional context message
    """
    logger.exception(f"{message}: {exc}")