Coverage for .claude/hooks/moai/lib/error_handler.py: 0.00%

63 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-19 08:00 +0900

1#!/usr/bin/env python3 

2"""Standard Error Handler for Alfred Hooks 

3 

4Provides consistent error handling, logging, and response formatting across all hooks. 

5""" 

6 

7import json 

8import logging 

9import os 

10import sys 

11import time 

12from datetime import datetime 

13from typing import Any, Dict, Optional 

14 

15from lib.config_manager import get_config_manager, get_timeout_seconds 

16 

17 

18class HookErrorHandler: 

19 """Standardized error handling for Alfred hooks. 

20 

21 Provides consistent error responses, logging, and graceful degradation. 

22 """ 

23 

24 def __init__(self, hook_name: str, timeout_seconds: Optional[int] = None): 

25 """Initialize error handler. 

26 

27 Args: 

28 hook_name: Name of the hook for logging 

29 timeout_seconds: Default timeout for operations (uses config if None) 

30 """ 

31 self.hook_name = hook_name 

32 self.timeout_seconds = timeout_seconds or get_timeout_seconds() 

33 self.start_time = time.time() 

34 

35 # Setup logging 

36 self._setup_logging() 

37 

38 def _setup_logging(self): 

39 """Setup logging configuration.""" 

40 # Create logger with hook name 

41 self.logger = logging.getLogger(f"alfred.hooks.{self.hook_name}") 

42 

43 # Create console handler if not exists 

44 if not self.logger.handlers: 

45 handler = logging.StreamHandler(sys.stderr) 

46 formatter = logging.Formatter( 

47 f"[{self.hook_name}] [%(levelname)s] %(message)s" 

48 ) 

49 handler.setFormatter(formatter) 

50 self.logger.addHandler(handler) 

51 

52 # Set level based on environment 

53 log_level = getattr(logging, os.environ.get("HOOK_LOG_LEVEL", "WARNING").upper()) 

54 self.logger.setLevel(log_level) 

55 

56 def create_response( 

57 self, 

58 success: bool = True, 

59 error: Optional[str] = None, 

60 message: Optional[str] = None, 

61 data: Optional[Dict[str, Any]] = None, 

62 graceful_degradation: bool = False, 

63 execution_time: Optional[float] = None 

64 ) -> Dict[str, Any]: 

65 """Create standardized response structure. 

66 

67 Args: 

68 success: Whether operation was successful 

69 error: Error message if failed 

70 message: Descriptive message 

71 data: Additional data payload 

72 graceful_degradation: Whether graceful degradation was applied 

73 execution_time: How long the operation took 

74 

75 Returns: 

76 Standardized response dictionary 

77 """ 

78 response = { 

79 "continue": True, # Always allow operation to continue by default 

80 "success": success, 

81 "hook_name": self.hook_name, 

82 "timestamp": datetime.now().isoformat(), 

83 "execution_time": execution_time or (time.time() - self.start_time), 

84 "graceful_degradation": graceful_degradation, 

85 } 

86 

87 if error: 

88 response["error"] = error 

89 if message: 

90 response["message"] = message 

91 if data: 

92 response["data"] = data 

93 

94 return response 

95 

96 def handle_timeout(self, operation_name: str = "operation") -> Dict[str, Any]: 

97 """Handle timeout errors consistently. 

98 

99 Args: 

100 operation_name: Name of the timed-out operation 

101 

102 Returns: 

103 Timeout error response 

104 """ 

105 error_msg = f"{operation_name} timed out after {self.timeout_seconds} seconds" 

106 self.logger.warning(error_msg) 

107 

108 # Get localized message from config 

109 message = get_config_manager().get_message("timeout", self.hook_name, "timeout") 

110 if message == f"Message not found: timeout.{self.hook_name}.timeout": 

111 message = f"{operation_name} timed out after {self.timeout_seconds} seconds" 

112 

113 return self.create_response( 

114 success=False, 

115 error=error_msg, 

116 message=message, 

117 graceful_degradation=True 

118 ) 

119 

120 def handle_json_error(self, error: Exception, context: str = "JSON parsing") -> Dict[str, Any]: 

121 """Handle JSON parsing errors consistently. 

122 

123 Args: 

124 error: The JSONDecodeError exception 

125 context: Context where the error occurred 

126 

127 Returns: 

128 JSON error response 

129 """ 

130 error_msg = f"{context} error: {str(error)}" 

131 self.logger.error(error_msg) 

132 

133 return self.create_response( 

134 success=False, 

135 error=error_msg, 

136 message=f"⚠️ {error_msg}", 

137 graceful_degradation=True 

138 ) 

139 

140 def handle_import_error(self, module_name: str) -> Dict[str, Any]: 

141 """Handle import errors with fallback information. 

142 

143 Args: 

144 module_name: Name of the module that failed to import 

145 

146 Returns: 

147 Import error response 

148 """ 

149 error_msg = f"Module '{module_name}' not available" 

150 self.logger.error(error_msg) 

151 

152 return self.create_response( 

153 success=False, 

154 error=error_msg, 

155 message=f"⚠️ {error_msg}. Using fallback implementation.", 

156 graceful_degradation=True 

157 ) 

158 

159 def handle_generic_error(self, error: Exception, context: str = "operation") -> Dict[str, Any]: 

160 """Handle generic exceptions consistently. 

161 

162 Args: 

163 error: The exception that occurred 

164 context: Context where the error occurred 

165 

166 Returns: 

167 Generic error response 

168 """ 

169 error_msg = f"{context} error: {str(error)}" 

170 self.logger.error(error_msg) 

171 

172 return self.create_response( 

173 success=False, 

174 error=error_msg, 

175 message=f"{error_msg}", 

176 graceful_degradation=True 

177 ) 

178 

179 def handle_config_error(self, error: Exception, config_path: str) -> Dict[str, Any]: 

180 """Handle configuration loading errors. 

181 

182 Args: 

183 error: The exception that occurred 

184 config_path: Path to the config file 

185 

186 Returns: 

187 Config error response 

188 """ 

189 error_msg = f"Failed to load config from {config_path}: {str(error)}" 

190 self.logger.error(error_msg) 

191 

192 return self.create_response( 

193 success=False, 

194 error=error_msg, 

195 message=f"⚙️ {error_msg}. Using defaults.", 

196 graceful_degradation=True 

197 ) 

198 

199 def handle_success(self, message: str = "Operation completed successfully", data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: 

200 """Handle successful operations. 

201 

202 Args: 

203 message: Success message 

204 data: Additional data to include 

205 

206 Returns: 

207 Success response 

208 """ 

209 self.logger.info(message) 

210 

211 return self.create_response( 

212 success=True, 

213 message=message, 

214 data=data 

215 ) 

216 

217 def create_success(self, message: str = "Operation completed successfully", data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: 

218 """Alias for handle_success for backward compatibility. 

219 

220 Args: 

221 message: Success message 

222 data: Additional data to include 

223 

224 Returns: 

225 Success response (same as handle_success) 

226 """ 

227 return self.handle_success(message, data) 

228 

229 def print_and_exit(self, response: Dict[str, Any], exit_code: int = 0): 

230 """Print response and exit with appropriate code. 

231 

232 Args: 

233 response: Response to print 

234 exit_code: Exit code (0=success, 1=error, 2=critical, 3=config) 

235 """ 

236 print(json.dumps(response, ensure_ascii=False, indent=2)) 

237 sys.exit(exit_code)