Coverage for src / sql_tool / core / logging.py: 100%
13 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 15:28 -0500
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 15:28 -0500
1"""Logging configuration using structlog.
3Logs go to stderr to keep stdout clean for data output (piping).
4"""
6import logging
7import sys
8from typing import Any
10import structlog
12_LOG_LEVELS: dict[str, int] = {
13 "debug": logging.DEBUG,
14 "info": logging.INFO,
15 "warning": logging.WARNING,
16 "error": logging.ERROR,
17 "critical": logging.CRITICAL,
18}
21def setup_logging(verbose: bool = False) -> None:
22 """Configure structlog for SQL Tool.
24 Args:
25 verbose: If True, set log level to DEBUG. Otherwise INFO.
26 """
27 log_level = "debug" if verbose else "info"
29 structlog.configure(
30 processors=[
31 structlog.contextvars.merge_contextvars,
32 structlog.processors.add_log_level,
33 structlog.processors.TimeStamper(fmt="iso"),
34 structlog.dev.ConsoleRenderer(colors=sys.stderr.isatty()),
35 ],
36 wrapper_class=structlog.make_filtering_bound_logger(_LOG_LEVELS[log_level]),
37 context_class=dict,
38 logger_factory=structlog.PrintLoggerFactory(file=sys.stderr),
39 cache_logger_on_first_use=True,
40 )
43def get_logger(name: str | None = None) -> Any:
44 """Get a structlog logger, optionally bound with a name.
46 IMPORTANT: Never call this at module level. Always call inside
47 functions or __init__() after setup_logging() has been called.
48 """
49 logger = structlog.get_logger()
50 if name:
51 logger = logger.bind(logger=name)
52 return logger