11"""Logger configuration"""
22
33import logging
4- import sys
4+ import os
5+ import platform
6+ from pathlib import Path
7+ from datetime import datetime , timezone
58
69import structlog
710
811
912# Configure structlog with human-readable output
13+ def _default_log_path () -> str :
14+ env_path = os .getenv ("WHISPR_LOG_PATH" )
15+ if env_path :
16+ return env_path
17+
18+ if os .name == "nt" :
19+ base_dir = os .getenv ("PROGRAMDATA" ) or os .getenv ("LOCALAPPDATA" ) or str (Path .home ())
20+ else :
21+ base_dir = "/var/log"
22+ return str (Path (base_dir ) / "whispr" / "access.log" )
23+
24+
25+ def _resolve_log_path () -> str :
26+ log_path = _default_log_path ()
27+ if _ensure_writable_log_path (log_path ):
28+ return log_path
29+
30+ if platform .system () == "Darwin" :
31+ fallback_dir = Path .home () / "Library" / "Logs" / "whispr"
32+ else :
33+ fallback_dir = Path .home () / ".local" / "state" / "whispr"
34+ fallback_path = str (fallback_dir / "access.log" )
35+ if _ensure_writable_log_path (fallback_path ):
36+ return fallback_path
37+
38+ return str (Path .cwd () / "whispr_access.log" )
39+
40+
41+ def _ensure_writable_log_path (log_path : str ) -> bool :
42+ log_file = Path (log_path )
43+ try :
44+ log_file .parent .mkdir (parents = True , exist_ok = True )
45+ with log_file .open ("a" , encoding = "utf-8" ):
46+ pass
47+ return True
48+ except OSError :
49+ return False
50+
51+
1052def setup_structlog () -> structlog .BoundLogger :
1153 """Initializes a structured logger"""
54+ log_path = _resolve_log_path ()
55+
1256 structlog .configure (
1357 processors = [
1458 structlog .stdlib .filter_by_level ,
@@ -27,11 +71,25 @@ def setup_structlog() -> structlog.BoundLogger:
2771 )
2872
2973 # Set up basic configuration for the standard library logging
30- logging .basicConfig (format = "%(message)s" , stream = sys . stdout , level = logging .ERROR )
74+ logging .basicConfig (format = "%(message)s" , handlers = [ logging . FileHandler ( log_path )] , level = logging .INFO )
3175
3276 # Return the structlog logger instance
3377 return structlog .get_logger ()
3478
3579
3680# Initialize logger
3781logger = setup_structlog ()
82+
83+
84+ def log_secret_fetch (
85+ logger_instance : structlog .BoundLogger ,
86+ secret_name : str ,
87+ vault_type : str ,
88+ ) -> None :
89+ """Log a fetched secret with a timezone-aware timestamp."""
90+ logger_instance .info (
91+ "Secret fetched" ,
92+ secret_name = secret_name ,
93+ vault_type = vault_type ,
94+ fetched_at = datetime .now (timezone .utc ).isoformat (),
95+ )
0 commit comments