feat: global exception middleware (#6017)

* global exception middleware

* redo both logging middlewares as one

* remove extra middleware files
This commit is contained in:
Kian Jones
2025-11-06 20:34:52 -08:00
committed by Caren Thomas
parent a217c8f1b6
commit 4acda9c80f
8 changed files with 644 additions and 70 deletions

137
letta/exceptions/logging.py Normal file
View File

@@ -0,0 +1,137 @@
"""
Helper utilities for structured exception logging.
Use these when you need to add context to exceptions before raising them.
"""
from typing import Any, Dict, Optional
from letta.log import get_logger
logger = get_logger(__name__)
def log_and_raise(
exception: Exception,
message: str,
context: Optional[Dict[str, Any]] = None,
level: str = "error",
) -> None:
"""
Log an exception with structured context and then raise it.
This is useful when you want to ensure an exception is logged with
full context before raising it.
Args:
exception: The exception to log and raise
message: Human-readable message to log
context: Additional context to include in logs (dict)
level: Log level (default: "error")
Example:
try:
result = some_operation()
except ValueError as e:
log_and_raise(
e,
"Failed to process operation",
context={
"user_id": user.id,
"operation": "some_operation",
"input": input_data,
}
)
"""
extra = {
"exception_type": exception.__class__.__name__,
"exception_message": str(exception),
"exception_module": exception.__class__.__module__,
}
if context:
extra.update(context)
log_method = getattr(logger, level.lower())
log_method(
f"{message}: {exception.__class__.__name__}: {str(exception)}",
extra=extra,
exc_info=exception,
)
raise exception
def log_exception(
exception: Exception,
message: str,
context: Optional[Dict[str, Any]] = None,
level: str = "error",
) -> None:
"""
Log an exception with structured context without raising it.
Use this when you want to log an exception but handle it gracefully.
Args:
exception: The exception to log
message: Human-readable message to log
context: Additional context to include in logs (dict)
level: Log level (default: "error")
Example:
try:
result = some_operation()
except ValueError as e:
log_exception(
e,
"Operation failed, using fallback",
context={"user_id": user.id}
)
result = fallback_operation()
"""
extra = {
"exception_type": exception.__class__.__name__,
"exception_message": str(exception),
"exception_module": exception.__class__.__module__,
}
if context:
extra.update(context)
log_method = getattr(logger, level.lower())
log_method(
f"{message}: {exception.__class__.__name__}: {str(exception)}",
extra=extra,
exc_info=exception,
)
def add_exception_context(exception: Exception, **context) -> Exception:
"""
Add context to an exception that will be picked up by the global exception handler.
This attaches a __letta_context__ attribute to the exception with structured data.
The global exception handler will automatically include this context in logs.
Args:
exception: The exception to add context to
**context: Key-value pairs to add as context
Returns:
The same exception with context attached
Example:
try:
result = operation()
except ValueError as e:
raise add_exception_context(
e,
user_id=user.id,
operation="do_thing",
input_data=data,
)
"""
if not hasattr(exception, "__letta_context__"):
exception.__letta_context__ = {}
exception.__letta_context__.update(context)
return exception