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

1""" 

2Centralized utility for logging settings and configuration. 

3Controls when and how settings are logged based on environment variables. 

4 

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""" 

11 

12import os 

13from typing import Any, Dict, Optional 

14from loguru import logger 

15 

16 

17# Check environment variable once at module load 

18SETTINGS_LOG_LEVEL = os.getenv("LDR_LOG_SETTINGS", "none").lower() 

19 

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" 

32 

33 

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. 

41 

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) 

46 

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 

54 

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 

57 

58 if log_level == "summary": 

59 # Log only summary at INFO level 

60 summary = create_settings_summary(settings) 

61 logger.info(f"{message}: {summary}") 

62 

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}") 

70 

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 ) 

77 

78 

79def redact_sensitive_keys(settings: Dict[str, Any]) -> Dict[str, Any]: 

80 """ 

81 Redact sensitive keys from settings dictionary. 

82 

83 Args: 

84 settings: Settings dictionary 

85 

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 ] 

99 

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 ) 

107 

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 

121 

122 return redacted 

123 

124 

125def create_settings_summary(settings: Any) -> str: 

126 """ 

127 Create a summary of settings for logging. 

128 

129 Args: 

130 settings: Settings object or dict 

131 

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) 

140 

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__}" 

144 

145 

146def get_settings_log_level() -> str: 

147 """ 

148 Get the current settings logging level. 

149 

150 Returns: 

151 Current log level: "none", "summary", "debug", or "debug_unsafe" 

152 """ 

153 return SETTINGS_LOG_LEVEL