Coverage for src / sentry_tool / monitoring.py: 100.00%
26 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-28 19:20 -0500
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-28 19:20 -0500
1"""Monitoring setup: Sentry error tracking and structlog logging.
3Logging goes to stderr to keep stdout clean for data output (piping).
4Sentry is initialized after logging for self-monitoring.
5DSN resolution order: SENTRY_DSN env var > config file sentry_dsn > hardcoded default.
6"""
8import logging
9import os
10import sys
11from typing import Any
13import sentry_sdk
14import structlog
16from sentry_tool.__about__ import __version__
17from sentry_tool.config import load_config
19_LOG_LEVELS: dict[str, int] = {
20 "debug": logging.DEBUG,
21 "info": logging.INFO,
22 "warning": logging.WARNING,
23 "error": logging.ERROR,
24 "critical": logging.CRITICAL,
25}
28def setup_logging(verbose: bool = False) -> None:
29 log_level = "debug" if verbose else "info"
31 structlog.configure(
32 processors=[
33 structlog.contextvars.merge_contextvars,
34 structlog.processors.add_log_level,
35 structlog.processors.TimeStamper(fmt="iso"),
36 structlog.dev.ConsoleRenderer(colors=sys.stderr.isatty()),
37 ],
38 wrapper_class=structlog.make_filtering_bound_logger(_LOG_LEVELS[log_level]),
39 context_class=dict,
40 logger_factory=structlog.PrintLoggerFactory(file=sys.stderr),
41 cache_logger_on_first_use=True,
42 )
45def get_logger(name: str | None = None) -> Any:
46 logger = structlog.get_logger()
47 if name:
48 logger = logger.bind(logger=name)
49 return logger
52_DEFAULT_DSN = "https://a176b6acecc8529b8f985532d49e2e04@o4508594232426496.ingest.us.sentry.io/4510896961093633"
55def resolve_dsn() -> str | None:
56 """Check SENTRY_DSN env var first, then config file sentry_dsn field.
58 Returns an override DSN if configured, or None to use the hardcoded default.
59 """
60 dsn = os.environ.get("SENTRY_DSN")
61 if dsn:
62 return dsn
64 config = load_config()
65 return config.sentry_dsn
68def setup_sentry(environment: str = "local") -> None:
69 sentry_sdk.init(
70 dsn=resolve_dsn() or _DEFAULT_DSN,
71 traces_sample_rate=0.03,
72 environment=environment,
73 release=__version__,
74 attach_stacktrace=True,
75 send_default_pii=False,
76 )