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
« 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
4Provides consistent error handling, logging, and response formatting across all hooks.
5"""
7import json
8import logging
9import os
10import sys
11import time
12from datetime import datetime
13from typing import Any, Dict, Optional
15from lib.config_manager import get_config_manager, get_timeout_seconds
18class HookErrorHandler:
19 """Standardized error handling for Alfred hooks.
21 Provides consistent error responses, logging, and graceful degradation.
22 """
24 def __init__(self, hook_name: str, timeout_seconds: Optional[int] = None):
25 """Initialize error handler.
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()
35 # Setup logging
36 self._setup_logging()
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}")
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)
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)
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.
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
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 }
87 if error:
88 response["error"] = error
89 if message:
90 response["message"] = message
91 if data:
92 response["data"] = data
94 return response
96 def handle_timeout(self, operation_name: str = "operation") -> Dict[str, Any]:
97 """Handle timeout errors consistently.
99 Args:
100 operation_name: Name of the timed-out operation
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)
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"
113 return self.create_response(
114 success=False,
115 error=error_msg,
116 message=message,
117 graceful_degradation=True
118 )
120 def handle_json_error(self, error: Exception, context: str = "JSON parsing") -> Dict[str, Any]:
121 """Handle JSON parsing errors consistently.
123 Args:
124 error: The JSONDecodeError exception
125 context: Context where the error occurred
127 Returns:
128 JSON error response
129 """
130 error_msg = f"{context} error: {str(error)}"
131 self.logger.error(error_msg)
133 return self.create_response(
134 success=False,
135 error=error_msg,
136 message=f"⚠️ {error_msg}",
137 graceful_degradation=True
138 )
140 def handle_import_error(self, module_name: str) -> Dict[str, Any]:
141 """Handle import errors with fallback information.
143 Args:
144 module_name: Name of the module that failed to import
146 Returns:
147 Import error response
148 """
149 error_msg = f"Module '{module_name}' not available"
150 self.logger.error(error_msg)
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 )
159 def handle_generic_error(self, error: Exception, context: str = "operation") -> Dict[str, Any]:
160 """Handle generic exceptions consistently.
162 Args:
163 error: The exception that occurred
164 context: Context where the error occurred
166 Returns:
167 Generic error response
168 """
169 error_msg = f"{context} error: {str(error)}"
170 self.logger.error(error_msg)
172 return self.create_response(
173 success=False,
174 error=error_msg,
175 message=f"❌ {error_msg}",
176 graceful_degradation=True
177 )
179 def handle_config_error(self, error: Exception, config_path: str) -> Dict[str, Any]:
180 """Handle configuration loading errors.
182 Args:
183 error: The exception that occurred
184 config_path: Path to the config file
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)
192 return self.create_response(
193 success=False,
194 error=error_msg,
195 message=f"⚙️ {error_msg}. Using defaults.",
196 graceful_degradation=True
197 )
199 def handle_success(self, message: str = "Operation completed successfully", data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
200 """Handle successful operations.
202 Args:
203 message: Success message
204 data: Additional data to include
206 Returns:
207 Success response
208 """
209 self.logger.info(message)
211 return self.create_response(
212 success=True,
213 message=message,
214 data=data
215 )
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.
220 Args:
221 message: Success message
222 data: Additional data to include
224 Returns:
225 Success response (same as handle_success)
226 """
227 return self.handle_success(message, data)
229 def print_and_exit(self, response: Dict[str, Any], exit_code: int = 0):
230 """Print response and exit with appropriate code.
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)