Source code for kelvin.logs
"""Kelvin application logging configuration.
This module provides structured logging configuration for Kelvin applications
using structlog. It automatically configures console rendering for interactive
sessions and JSON rendering for production environments.
Main Components:
configure_logger: Initialize the structlog logging configuration.
logger: Pre-configured structlog logger instance.
iso_datetime_processor: Processor to convert datetime values to ISO strings.
Example:
>>> from kelvin.logs import logger, configure_logger
>>> configure_logger()
>>> logger.info("Application started", version="1.0.0")
"""
from __future__ import annotations
import sys
from collections.abc import Mapping, MutableMapping
from datetime import datetime
from typing import Any
import structlog
[docs]
def iso_datetime_processor(_logger: Any, _method: str, event_dict: MutableMapping[str, Any]) -> Mapping[str, Any]:
"""Scan the event_dict for datetime values and convert them to ISO strings."""
for key, value in list(event_dict.items()):
if isinstance(value, datetime):
event_dict[key] = value.isoformat()
return event_dict
[docs]
def configure_logger(*_args: Any, **_initial_values: Any) -> None:
"""Configure structlog with standard processors for logging."""
if not structlog.is_configured():
is_tty = sys.stdout.isatty()
# Exception rendering differs per renderer and the two traceback processors are
# mutually exclusive:
# - ConsoleRenderer (dev/tty) formats `exc_info` itself into a pretty traceback,
# so it must NOT be preceded by dict_tracebacks/format_exc_info — pairing them
# makes ConsoleRenderer try to concatenate a list, raising
# `TypeError: can only concatenate str (not "list") to str` and masking the
# real exception.
# - JSONRenderer (prod/non-tty) cannot format `exc_info` on its own, so it relies
# on dict_tracebacks to expand it into structured frames first.
traceback_processors: list[Any] = [] if is_tty else [structlog.processors.dict_tracebacks]
structlog.configure_once(
processors=[
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
*traceback_processors,
structlog.processors.TimeStamper(fmt="iso", utc=True),
iso_datetime_processor,
structlog.dev.ConsoleRenderer() if is_tty else structlog.processors.JSONRenderer(),
],
cache_logger_on_first_use=True,
)
logger = structlog.stdlib.get_logger()