Coverage for src / local_deep_research / settings / logger.py: 18%
53 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""
2Centralized utility for logging settings and configuration.
3Controls when and how settings are logged based on environment variables.
5Environment variable LDR_LOG_SETTINGS controls the verbosity:
6- "none" or "false": No settings logging at all (default)
7- "summary" or "info": Only log count and summary of settings
8- "debug" or "full": Log complete settings (with sensitive keys redacted)
9- "debug_unsafe": Log complete settings including sensitive keys (DANGEROUS - only for debugging)
10"""
12import os
13from typing import Any, Dict, Optional
14from loguru import logger
17# Check environment variable once at module load
18SETTINGS_LOG_LEVEL = os.getenv("LDR_LOG_SETTINGS", "none").lower()
20# Map various values to standardized levels
21if SETTINGS_LOG_LEVEL in ("false", "0", "no", "none", "off"): 21 ↛ 23line 21 didn't jump to line 23 because the condition on line 21 was always true
22 SETTINGS_LOG_LEVEL = "none"
23elif SETTINGS_LOG_LEVEL in ("true", "1", "yes", "info", "summary"):
24 SETTINGS_LOG_LEVEL = "summary"
25elif SETTINGS_LOG_LEVEL in ("debug", "full", "all"):
26 SETTINGS_LOG_LEVEL = "debug"
27elif SETTINGS_LOG_LEVEL in ("debug_unsafe", "unsafe", "raw"):
28 SETTINGS_LOG_LEVEL = "debug_unsafe"
29else:
30 # Invalid value, default to none
31 SETTINGS_LOG_LEVEL = "none"
34def log_settings(
35 settings: Any,
36 message: str = "Settings loaded",
37 force_level: Optional[str] = None,
38) -> None:
39 """
40 Centralized settings logging with conditional output based on LDR_LOG_SETTINGS env var.
42 Args:
43 settings: Settings object or dict to log
44 message: Log message prefix
45 force_level: Override the environment variable setting (for critical messages)
47 Behavior based on LDR_LOG_SETTINGS:
48 - "none": No output
49 - "summary": Log count and basic info at INFO level
50 - "debug": Log full settings at DEBUG level (sensitive keys redacted)
51 - "debug_unsafe": Log full settings at DEBUG level (nothing redacted - DANGEROUS)
52 """
53 log_level = force_level or SETTINGS_LOG_LEVEL
55 if log_level == "none": 55 ↛ 58line 55 didn't jump to line 58 because the condition on line 55 was always true
56 return
58 if log_level == "summary":
59 # Log only summary at INFO level
60 summary = create_settings_summary(settings)
61 logger.info(f"{message}: {summary}")
63 elif log_level == "debug":
64 # Log full settings at DEBUG level with redaction
65 if isinstance(settings, dict):
66 safe_settings = redact_sensitive_keys(settings)
67 logger.debug(f"{message} (redacted): {safe_settings}")
68 else:
69 logger.debug(f"{message}: {settings}")
71 elif log_level == "debug_unsafe":
72 # Log full settings at DEBUG level without redaction (DANGEROUS)
73 logger.debug(f"{message} (UNSAFE - includes secrets): {settings}")
74 logger.warning(
75 "Settings logged with sensitive information - use only for debugging!"
76 )
79def redact_sensitive_keys(settings: Dict[str, Any]) -> Dict[str, Any]:
80 """
81 Redact sensitive keys from settings dictionary.
83 Args:
84 settings: Settings dictionary
86 Returns:
87 Settings dictionary with sensitive values redacted
88 """
89 sensitive_patterns = [
90 "api_key",
91 "apikey",
92 "password",
93 "secret",
94 "token",
95 "credential",
96 "auth",
97 "private",
98 ]
100 redacted = {}
101 for key, value in settings.items():
102 # Check if key contains sensitive patterns
103 key_lower = key.lower()
104 is_sensitive = any(
105 pattern in key_lower for pattern in sensitive_patterns
106 )
108 if is_sensitive:
109 # Redact the value
110 if isinstance(value, dict) and "value" in value:
111 redacted[key] = {**value, "value": "***REDACTED***"}
112 elif isinstance(value, str):
113 redacted[key] = "***REDACTED***"
114 else:
115 redacted[key] = "***REDACTED***"
116 elif isinstance(value, dict):
117 # Recursively redact nested dicts
118 redacted[key] = redact_sensitive_keys(value)
119 else:
120 redacted[key] = value
122 return redacted
125def create_settings_summary(settings: Any) -> str:
126 """
127 Create a summary of settings for logging.
129 Args:
130 settings: Settings object or dict
132 Returns:
133 Summary string
134 """
135 if isinstance(settings, dict):
136 # Count different types of settings
137 search_engines = sum(1 for k in settings.keys() if "search.engine" in k)
138 llm_settings = sum(1 for k in settings.keys() if "llm." in k)
139 total = len(settings)
141 return f"{total} total settings (search engines: {search_engines}, LLM: {llm_settings})"
142 else:
143 return f"Settings object of type {type(settings).__name__}"
146def get_settings_log_level() -> str:
147 """
148 Get the current settings logging level.
150 Returns:
151 Current log level: "none", "summary", "debug", or "debug_unsafe"
152 """
153 return SETTINGS_LOG_LEVEL