summaryrefslogtreecommitdiff
path: root/shared/src
diff options
context:
space:
mode:
Diffstat (limited to 'shared/src')
-rw-r--r--shared/src/shared/logging.py70
1 files changed, 70 insertions, 0 deletions
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)