feat: support logging of exceptions#277
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces exception logging capabilities to the logger, including a new exception function to log active stack traces and utility functions to safely serialize exceptions and handle circular references in exception arguments. Comprehensive tests have been added to verify these changes. The review feedback suggests improving type safety by changing the type of the refs parameter from set[_typing.Any] to set[int] in both _exception_from_args and _remove_circular, as it is used to track object IDs.
| def _exception_from_args( | ||
| exception: BaseException, refs: set[_typing.Any] | None = None | ||
| ) -> dict[str, _typing.Any]: |
There was a problem hiding this comment.
The refs parameter stores the unique integer IDs (id(obj)) of objects to track circular references. Therefore, typing it as set[int] is more precise and type-safe than set[_typing.Any].
| def _exception_from_args( | |
| exception: BaseException, refs: set[_typing.Any] | None = None | |
| ) -> dict[str, _typing.Any]: | |
| def _exception_from_args( | |
| exception: BaseException, refs: set[int] | None = None | |
| ) -> dict[str, _typing.Any]: |
| return exception.__class__.__name__ | ||
|
|
||
|
|
||
| def _remove_circular(obj: _typing.Any, refs: set[_typing.Any] | None = None): |
There was a problem hiding this comment.
The refs parameter stores the unique integer IDs (id(obj)) of objects to track circular references. Therefore, typing it as set[int] is more precise and type-safe than set[_typing.Any].
| def _remove_circular(obj: _typing.Any, refs: set[_typing.Any] | None = None): | |
| def _remove_circular(obj: _typing.Any, refs: set[int] | None = None): |
Summary
Add exception-aware logging support so
firebase_functions.loggercan accept exception objects without failing JSON serialization, and include stack trace information in logged output.Problem/Root Cause
Issue #172 reports that the current logging API does not handle exceptions properly. Passing an exception to
logger.error(..., error=e)can raise a JSON serialization error instead of writing a log entry, and the API does not provide a direct way to log full exception stack traces.The root cause is that exception objects were being passed through the existing log serialization path without being converted into a JSON-safe structure. That path handled common container types and circular references, but it did not recognize
BaseExceptionvalues or extract structured exception details such as type, message, arguments, and traceback information.Solution/Changes
Add exception-specific serialization in the logger so
BaseExceptionvalues are converted into a JSON-safe dictionary containing the exception type, message, arguments, and, when available, a formatted stack trace.Update circular-reference handling so exceptions and exception payloads can be serialized safely even when they contain self-referential data. The PR also adds a new
logger.exception(...)helper that logs at error severity and attaches the active exception stack trace directly to the top-level log entry.Testing
tests/test_logger.pycoveringlogger.error(..., error=exception)with normal exceptions, self-referential exception arguments, and cyclic payloads.logger.exception(...)and verifying that the emitted log includes a stack trace.