Skip to content

Commit 699a3d8

Browse files
Adding exception fields
1 parent c9adbee commit 699a3d8

File tree

3 files changed

+120
-6
lines changed

3 files changed

+120
-6
lines changed

aws_lambda_powertools/logging/buffer/functions.py

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,63 @@
11
from __future__ import annotations
22

33
import inspect
4+
import sys
45
import time
5-
from typing import Any, Mapping
6+
from typing import TYPE_CHECKING, Any, Mapping
7+
8+
if TYPE_CHECKING:
9+
import logging
610

711

812
def _create_buffer_record(
913
level: int,
1014
msg: object,
1115
args: object,
16+
exc_info: logging._ExcInfoType = None,
17+
stack_info: bool = False,
1218
extra: Mapping[str, object] | None = None,
1319
) -> dict[str, Any]:
20+
"""
21+
Create a structured log record for buffering to save in buffer.
22+
23+
Parameters
24+
----------
25+
level : int
26+
Logging level (e.g., logging.DEBUG, logging.INFO) indicating log severity.
27+
msg : object
28+
The log message to be recorded.
29+
args : object
30+
Additional arguments associated with the log message.
31+
exc_info : logging._ExcInfoType, optional
32+
Exception information to be included in the log record.
33+
If None, no exception details will be captured.
34+
stack_info : bool, default False
35+
Flag to include stack trace information in the log record.
36+
extra : Mapping[str, object], optional
37+
Additional context or metadata to be attached to the log record.
38+
39+
Returns
40+
-------
41+
dict[str, Any]
42+
43+
Notes
44+
-----
45+
- Captures caller frame information for precise log source tracking
46+
- Automatically handles exception context
47+
"""
48+
# Retrieve the caller's frame information to capture precise log context
49+
# Uses inspect.stack() with index 3 to get the original caller's details
1450
caller_frame = inspect.stack()[3]
51+
52+
# Get the current timestamp
1553
timestamp = time.time()
1654

55+
# Dynamically replace exc_info with current system exception information
56+
# This ensures the most recent exception is captured if available
57+
if exc_info:
58+
exc_info = sys.exc_info()
59+
60+
# Construct and return the og record dictionary
1761
return {
1862
"level": level,
1963
"msg": msg,
@@ -23,11 +67,47 @@ def _create_buffer_record(
2367
"function": caller_frame.function,
2468
"extra": extra,
2569
"timestamp": timestamp,
70+
"exc_info": exc_info,
71+
"stack_info": stack_info,
2672
}
2773

2874

2975
def _check_minimum_buffer_log_level(buffer_log_level, current_log_level):
30-
# Define log level mapping
76+
"""
77+
Determine if the current log level meets or exceeds the buffer's minimum log level.
78+
79+
Compares log levels to decide whether a log message should be included in the buffer.
80+
81+
Parameters
82+
----------
83+
buffer_log_level : str
84+
Minimum log level configured for the buffer.
85+
Must be one of: 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'.
86+
current_log_level : str
87+
Log level of the current log message.
88+
Must be one of: 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'.
89+
90+
Returns
91+
-------
92+
bool
93+
True if the current log level is lower (more verbose) than the buffer's
94+
minimum log level, indicating the message should be buffered.
95+
False if the current log level is higher (less verbose) and should not be buffered.
96+
97+
Notes
98+
-----
99+
- Log levels are compared based on their numeric severity
100+
- Conversion to uppercase ensures case-insensitive comparisons
101+
102+
Examples
103+
--------
104+
>>> _check_minimum_buffer_log_level('INFO', 'DEBUG')
105+
True
106+
>>> _check_minimum_buffer_log_level('ERROR', 'WARNING')
107+
False
108+
"""
109+
# Predefined log level mapping with numeric severity values
110+
# Lower values indicate more verbose logging levels
31111
log_levels = {
32112
"DEBUG": 10,
33113
"INFO": 20,
@@ -36,7 +116,8 @@ def _check_minimum_buffer_log_level(buffer_log_level, current_log_level):
36116
"CRITICAL": 50,
37117
}
38118

39-
# Convert string levels to numeric if needed
119+
# Normalize input log levels to uppercase for consistent comparison
120+
# Retrieve corresponding numeric log level values
40121
buffer_level_num = log_levels.get(buffer_log_level.upper())
41122
current_level_num = log_levels.get(current_log_level.upper())
42123

aws_lambda_powertools/logging/logger.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ def _create_and_flush_log_record(self, log_line: dict) -> None:
10681068
lno=log_line["line"],
10691069
msg=log_line["msg"],
10701070
args=(),
1071-
exc_info=None,
1071+
exc_info=log_line["exc_info"],
10721072
func=log_line["function"],
10731073
extra=log_line["extra"],
10741074
)
@@ -1121,7 +1121,14 @@ def _add_log_record_to_buffer(
11211121
# Ensures logs from previous invocations do not leak into current execution
11221122
self.buffer_cache.clear()
11231123

1124-
log_record: dict[str, Any] = _create_buffer_record(level=level, msg=msg, args=args, extra=extra)
1124+
log_record: dict[str, Any] = _create_buffer_record(
1125+
level=level,
1126+
msg=msg,
1127+
args=args,
1128+
exc_info=exc_info,
1129+
stack_info=stack_info,
1130+
extra=extra,
1131+
)
11251132
self.buffer_cache.add(tracer_id, log_record)
11261133

11271134
def flush_buffer(self) -> None:

tests/functional/logger/required_dependencies/test_powertools_logger_buffer.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,33 @@ def test_ensure_log_location_after_flush_buffer(stdout, service_name, monkeypatc
187187

188188
# THEN Validate that the log location is precisely captured
189189
log = capture_multiple_logging_statements_output(stdout)
190-
assert "test_ensure_log_location_after_flush_buffer:184" in log[0]["location"]
190+
assert "test_ensure_log_location_after_flush_buffer" in log[0]["location"]
191+
192+
193+
def test_exception_logging_during_buffer_flush(stdout, service_name, monkeypatch):
194+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
195+
196+
# GIVEN A logger configured with a sufficiently large buffer
197+
logger_buffer_config = LoggerBufferConfig(max_size=10240)
198+
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
199+
200+
# Custom exception class
201+
class MyError(BaseException):
202+
pass
203+
204+
# WHEN Logging an exception and flushing the buffer
205+
try:
206+
raise MyError("Test exception message")
207+
except MyError as error:
208+
logger.debug("Logging a test exception to verify buffer and exception handling", exc_info=error)
209+
210+
logger.flush_buffer()
211+
212+
# THEN Validate that the log exception fields
213+
log = capture_multiple_logging_statements_output(stdout)
214+
assert log[0]["exception_name"] == "MyError"
215+
assert "Test exception message" in log[0]["exception"]
216+
assert "test_exception_logging_during_buffer_flush" in log[0]["exception"]
191217

192218

193219
def test_create_buffer_with_items_evicted(stdout, service_name, monkeypatch):

0 commit comments

Comments
 (0)