From cd15c3f64d00c6c97f738d59f719bb0938d9f7cb Mon Sep 17 00:00:00 2001 From: TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:06:46 +0900 Subject: feat(shared): add structlog-based structured logging --- shared/src/shared/logging.py | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 shared/src/shared/logging.py (limited to 'shared/src') diff --git a/shared/src/shared/logging.py b/shared/src/shared/logging.py new file mode 100644 index 0000000..b873eaf --- /dev/null +++ b/shared/src/shared/logging.py @@ -0,0 +1,70 @@ +"""Structured logging configuration using structlog.""" +from __future__ import annotations + +import logging +import sys + +import structlog + + +def setup_logging( + service_name: str, + log_level: str = "INFO", + log_format: str = "json", +) -> structlog.stdlib.BoundLogger: + """Configure structlog and stdlib logging, return a bound logger. + + Args: + service_name: Identifier bound as ``service`` context variable. + log_level: Root logger level (e.g. "DEBUG", "INFO", "WARNING"). + log_format: "json" for machine-readable output, "console" for + human-readable coloured output. + + Returns: + A structlog ``BoundLogger`` with ``service=service_name`` pre-bound. + """ + # Shared processors used by both structlog and stdlib formatter + shared_processors: list[structlog.types.Processor] = [ + structlog.contextvars.merge_contextvars, + structlog.stdlib.add_log_level, + structlog.stdlib.add_logger_name, + structlog.processors.TimeStamper(fmt="iso"), + structlog.processors.StackInfoRenderer(), + structlog.processors.UnicodeDecoder(), + ] + + # Choose renderer based on format + if log_format == "json": + renderer: structlog.types.Processor = structlog.processors.JSONRenderer() + else: + renderer = structlog.dev.ConsoleRenderer() + + # Configure structlog + structlog.configure( + processors=[ + *shared_processors, + structlog.stdlib.ProcessorFormatter.wrap_for_formatter, + ], + logger_factory=structlog.stdlib.LoggerFactory(), + wrapper_class=structlog.stdlib.BoundLogger, + cache_logger_on_first_use=False, + ) + + # Configure stdlib root logger + formatter = structlog.stdlib.ProcessorFormatter( + processors=[ + structlog.stdlib.ProcessorFormatter.remove_processors_meta, + renderer, + ], + ) + + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(formatter) + + root_logger = logging.getLogger() + root_logger.handlers.clear() + root_logger.addHandler(handler) + root_logger.setLevel(getattr(logging, log_level.upper(), logging.INFO)) + + # Return a logger with service name pre-bound + return structlog.get_logger(service=service_name) -- cgit v1.2.3