sec-rag-platform / src /secrag /core /logging.py
adwitiyashukla's picture
deploy space
6bcf4a2
Raw
History Blame Contribute Delete
2.5 kB
"""Structured logging.
Human-readable colourised output locally, single-line JSON in containers, and
the active trace id injected into every record so a log line can always be tied
back to the request that produced it.
"""
from __future__ import annotations
import logging
import sys
from typing import Any
import structlog
from structlog.types import EventDict, Processor
_CONFIGURED = False
def _add_trace_id(_logger: Any, _name: str, event_dict: EventDict) -> EventDict:
"""Attach the current trace id, if a trace is active."""
from secrag.observability.tracing import current_trace_id
if (trace_id := current_trace_id()) is not None:
event_dict.setdefault("trace_id", trace_id)
return event_dict
def configure_logging(level: str = "INFO", *, json_output: bool = False) -> None:
"""Configure structlog and the stdlib root logger. Idempotent."""
global _CONFIGURED
if _CONFIGURED:
return
shared: list[Processor] = [
structlog.contextvars.merge_contextvars,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.StackInfoRenderer(),
_add_trace_id,
]
renderer: Processor = (
structlog.processors.JSONRenderer()
if json_output
else structlog.dev.ConsoleRenderer(colors=sys.stderr.isatty())
)
structlog.configure(
processors=[*shared, structlog.processors.format_exc_info, renderer],
wrapper_class=structlog.make_filtering_bound_logger(
logging.getLevelNamesMapping().get(level.upper(), logging.INFO)
),
logger_factory=structlog.PrintLoggerFactory(file=sys.stderr),
cache_logger_on_first_use=True,
)
logging.basicConfig(format="%(message)s", stream=sys.stderr, level=level.upper())
# These libraries are chatty at INFO and drown out anything useful.
for noisy in ("httpx", "httpcore", "urllib3", "qdrant_client", "filelock"):
logging.getLogger(noisy).setLevel(logging.WARNING)
_CONFIGURED = True
def get_logger(name: str) -> structlog.stdlib.BoundLogger:
"""Return a bound logger, configuring logging on first use.
The module name is bound explicitly rather than via stdlib.add_logger_name,
because this stack renders through PrintLogger, which has no .name to read.
"""
if not _CONFIGURED:
configure_logging()
return structlog.get_logger().bind(logger=name) # type: ignore[no-any-return]