Spaces:
Running
Running
File size: 11,018 Bytes
27762e4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | """
Centralized error handling for StingrayExplorer.
This module provides a unified approach to handling exceptions across the application,
ensuring consistent error messages, proper logging, and better debugging capabilities.
Design Philosophy (Data Engineering Approach):
- Errors should be easily traceable with full stack traces
- Avoid hiding errors with excessive try/except logic
- Use centralized logging with rich metadata
- Support Panel's built-in logging (pn.state.log)
"""
import logging
import traceback
from typing import Dict, Type, Optional, Any, Tuple
from pathlib import Path
from datetime import datetime
class ErrorHandler:
"""
Centralized error handler for the StingrayExplorer application.
Provides:
- Consistent error message formatting
- Automatic logging with stack traces
- Exception type mapping to user-friendly messages
- Context tracking for better debugging
- Integration with Panel's logging system
"""
# Exception to user-friendly message mapping
ERROR_MESSAGES: Dict[Type[Exception], str] = {
FileNotFoundError: "The specified file could not be found. Please check the file path.",
PermissionError: "Permission denied. Please check file permissions.",
ValueError: "Invalid input provided. Please check your parameters.",
KeyError: "Required data not found. The file may be corrupted or in an unexpected format.",
IndexError: "Index out of range. Please check your data selection.",
TypeError: "Invalid data type encountered. Please check your input.",
OSError: "System error occurred. Please check file system access.",
MemoryError: "Insufficient memory. Try processing smaller datasets.",
AssertionError: "Data validation failed. Please check your input parameters.",
}
# Stingray-specific error patterns
STINGRAY_ERROR_PATTERNS = {
"No GTIs are equal to or longer than segment_size":
"No Good Time Intervals (GTIs) are long enough for the specified segment size. Please reduce the segment size or check your GTIs.",
"requested segment size":
"Invalid segment size. The dt value may be too large or segment size too small for the available data.",
"cannot convert":
"Data conversion failed. The file format may be incompatible or corrupted.",
"not enough values":
"Insufficient data points. The dataset may be too small or improperly formatted.",
"ConcurrentAppendException":
"Concurrent data access detected. This is usually temporary - please try again.",
}
@classmethod
def setup_logging(
cls,
log_dir: str = "logs",
log_level: int = logging.INFO,
app_name: str = "stingray_explorer"
):
"""
Configure logging for the application.
Args:
log_dir: Directory to store log files
log_level: Logging level (default: INFO)
app_name: Application name for log files
"""
# Create logs directory if it doesn't exist
log_path = Path(log_dir)
log_path.mkdir(exist_ok=True)
# Create log filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = log_path / f"{app_name}_{timestamp}.log"
# Configure logging format with rich metadata
log_format = (
"%(asctime)s [%(levelname)s] %(name)s - "
"%(filename)s:%(lineno)d - %(funcName)s() - %(message)s"
)
# Setup file handler with detailed logs
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(log_level)
file_handler.setFormatter(logging.Formatter(log_format))
# Setup console handler for warnings and errors only
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)
console_handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))
# Get root logger
logger = logging.getLogger()
logger.setLevel(log_level)
# Remove existing handlers to avoid duplicates
logger.handlers.clear()
# Add handlers
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logging.info(f"=== StingrayExplorer Logging Initialized ===")
logging.info(f"Log file: {log_file}")
logging.info(f"Log level: {logging.getLevelName(log_level)}")
@classmethod
def handle_error(
cls,
error: Exception,
context: str,
user_message: Optional[str] = None,
log_level: int = logging.ERROR,
log_to_panel: bool = True,
**context_data: Any
) -> Tuple[str, str]:
"""
Handle an exception with logging and user-friendly message generation.
This method logs the full stack trace for debugging while providing
user-friendly messages. Following data engineering best practices,
we preserve the full stack trace rather than hiding it.
Args:
error: The exception that was raised
context: String describing where the error occurred (e.g., "Loading event list")
user_message: Optional custom user-facing message
log_level: Logging level for this error (default: ERROR)
log_to_panel: Whether to also log to Panel's state.log (default: True)
**context_data: Additional context data to log (e.g., file_path="...", dt=1.0)
Returns:
Tuple of (user_message, technical_message)
"""
# Get the exception type
error_type = type(error)
error_str = str(error)
# Build context info string with metadata
context_info = f"Context: {context}"
if context_data:
context_details = ", ".join(f"{k}={v}" for k, v in context_data.items())
context_info += f" | Parameters: {context_details}"
# Get full stack trace
stack_trace = traceback.format_exc()
# Log the error with full details
logger = logging.getLogger(__name__)
log_message = (
f"\n{'='*80}\n"
f"{context_info}\n"
f"Exception Type: {error_type.__name__}\n"
f"Exception Message: {error_str}\n"
f"{'='*80}\n"
f"{stack_trace}"
f"{'='*80}"
)
logger.log(log_level, log_message)
# Also log to Panel's logging system if requested
if log_to_panel:
try:
import panel as pn
if pn.state.curdoc:
pn.state.log(f"{context}: {error_type.__name__} - {error_str}")
except (ImportError, AttributeError):
pass # Panel not available or not in a session
# Generate user-friendly message
if user_message:
final_message = user_message
else:
# Check for Stingray-specific error patterns first
final_message = None
for pattern, message in cls.STINGRAY_ERROR_PATTERNS.items():
if pattern in error_str:
final_message = message
break
# If no pattern match, use exception type mapping
if not final_message:
final_message = cls.ERROR_MESSAGES.get(
error_type,
f"An unexpected error occurred: {error_str}"
)
# Create technical message for advanced users/debugging
technical_message = f"{error_type.__name__}: {error_str}"
return final_message, technical_message
@classmethod
def handle_validation_error(
cls,
field_name: str,
value: Any,
expected: str,
context: str = "Input validation"
) -> Tuple[str, str]:
"""
Handle validation errors with specific field information.
Args:
field_name: Name of the field that failed validation
value: The invalid value
expected: Description of expected value
context: Context where validation occurred
Returns:
Tuple of (user_message, technical_message)
"""
user_message = f"Invalid {field_name}: Expected {expected}, got '{value}'"
technical_message = f"Validation failed for {field_name} in {context}"
logger = logging.getLogger(__name__)
logger.warning(
f"{context}: {technical_message}\n"
f"Field: {field_name}\n"
f"Value: {value}\n"
f"Expected: {expected}"
)
return user_message, technical_message
@classmethod
def handle_warning(
cls,
message: str,
context: str,
log_to_panel: bool = True,
**context_data: Any
) -> str:
"""
Handle warnings with logging.
Args:
message: Warning message
context: Context where warning occurred
log_to_panel: Whether to also log to Panel's state.log
**context_data: Additional context data
Returns:
User-facing warning message
"""
logger = logging.getLogger(__name__)
context_info = f"Context: {context}"
if context_data:
context_details = ", ".join(f"{k}={v}" for k, v in context_data.items())
context_info += f" | Parameters: {context_details}"
logger.warning(f"{context_info}\n{message}")
# Also log to Panel if requested
if log_to_panel:
try:
import panel as pn
if pn.state.curdoc:
pn.state.log(f"WARNING - {context}: {message}", level='warning')
except (ImportError, AttributeError):
pass
return f"Warning: {message}"
@classmethod
def log_info(
cls,
message: str,
context: str,
log_to_panel: bool = False,
**context_data: Any
):
"""
Log informational messages.
Args:
message: Info message
context: Context where this occurred
log_to_panel: Whether to also log to Panel's state.log
**context_data: Additional context data
"""
logger = logging.getLogger(__name__)
context_info = f"{context}"
if context_data:
context_details = ", ".join(f"{k}={v}" for k, v in context_data.items())
context_info += f" | {context_details}"
logger.info(f"{context_info}: {message}")
# Also log to Panel if requested
if log_to_panel:
try:
import panel as pn
if pn.state.curdoc:
pn.state.log(f"{context}: {message}", level='info')
except (ImportError, AttributeError):
pass
# Initialize logging when module is imported
ErrorHandler.setup_logging()
|