Skip to content

Commit c9adbee

Browse files
Adding more tests + propagating buffer configuration
1 parent c977904 commit c9adbee

File tree

2 files changed

+103
-17
lines changed

2 files changed

+103
-17
lines changed

aws_lambda_powertools/logging/logger.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,13 @@ def __init__(
273273
if self.buffer_config:
274274
self.buffer_cache = LoggerBufferCache(max_size_bytes=self.buffer_config.max_size)
275275

276-
self._init_logger(formatter_options=formatter_options, log_level=level, **kwargs)
276+
self._init_logger(
277+
formatter_options=formatter_options,
278+
log_level=level,
279+
buffer_config=self.buffer_config,
280+
buffer_cache=getattr(self, "buffer_cache", None),
281+
**kwargs,
282+
)
277283

278284
if self.log_uncaught_exceptions:
279285
logger.debug("Replacing exception hook")
@@ -317,6 +323,8 @@ def _init_logger(
317323
self,
318324
formatter_options: dict | None = None,
319325
log_level: str | int | None = None,
326+
buffer_config: LoggerBufferConfig | None = None,
327+
buffer_cache: LoggerBufferCache | None = None,
320328
**kwargs,
321329
) -> None:
322330
"""Configures new logger"""
@@ -332,6 +340,11 @@ def _init_logger(
332340
return
333341

334342
if is_logger_preconfigured:
343+
# Reuse existing buffer configuration from a previously configured logger
344+
# Ensures consistent buffer settings across logger instances within the same service
345+
# Enables buffer propagation and maintains a unified logging configuration
346+
self.buffer_config = self._logger.powertools_buffer_config # type: ignore[attr-defined]
347+
self.buffer_cache = self._logger.powertools_buffer_cache # type: ignore[attr-defined]
335348
return
336349

337350
self.setLevel(log_level)
@@ -356,6 +369,8 @@ def _init_logger(
356369
logger.debug(f"Marking logger {self.service} as preconfigured")
357370
self._logger.init = True # type: ignore[attr-defined]
358371
self._logger.powertools_handler = self.logger_handler # type: ignore[attr-defined]
372+
self._logger.powertools_buffer_config = buffer_config # type: ignore[attr-defined]
373+
self._logger.powertools_buffer_cache = buffer_cache # type: ignore[attr-defined]
359374

360375
def refresh_sample_rate_calculation(self) -> None:
361376
"""
@@ -721,6 +736,7 @@ def critical(
721736
# 1. Buffer configuration checked for immediate flush
722737
# 2. If auto-flush enabled, trigger complete buffer processing
723738
# 3. Critical log is not "bufferable", so ensure error log is immediately available
739+
724740
if self.buffer_config and self.buffer_config.flush_on_error:
725741
self.flush_buffer()
726742

@@ -1100,6 +1116,11 @@ def _add_log_record_to_buffer(
11001116
tracer_id = get_tracer_id()
11011117

11021118
if tracer_id:
1119+
if not self.buffer_cache.get(tracer_id):
1120+
# Detect new Lambda invocation context and reset buffer to maintain log isolation
1121+
# Ensures logs from previous invocations do not leak into current execution
1122+
self.buffer_cache.clear()
1123+
11031124
log_record: dict[str, Any] = _create_buffer_record(level=level, msg=msg, args=args, extra=extra)
11041125
self.buffer_cache.add(tracer_id, log_record)
11051126

tests/functional/logger/required_dependencies/test_powertools_logger_buffer.py

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def capture_multiple_logging_statements_output(stdout):
4747

4848

4949
@pytest.mark.parametrize("log_level", ["DEBUG", "WARNING", "INFO"])
50-
def test_logger_buffer_with_minimum_level_warning(log_level, stdout, service_name):
50+
def test_logger_buffer_with_minimum_level_warning(log_level, stdout, service_name, monkeypatch):
51+
52+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
5153

5254
# GIVEN A logger configured with a buffer and minimum log level set to WARNING
5355
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="WARNING")
@@ -100,7 +102,7 @@ def test_logger_buffer_is_never_buffered_with_error(stdout, service_name):
100102

101103
@pytest.mark.parametrize("log_level", ["CRITICAL", "ERROR"])
102104
def test_logger_buffer_is_flushed_when_an_error_happens(stdout, service_name, log_level, monkeypatch):
103-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
105+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
104106

105107
# GIVEN A logger configured with buffer and automatic error-based flushing
106108
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG", flush_on_error=True)
@@ -129,7 +131,7 @@ def test_logger_buffer_is_flushed_when_an_error_happens(stdout, service_name, lo
129131

130132
@pytest.mark.parametrize("log_level", ["CRITICAL", "ERROR"])
131133
def test_logger_buffer_is_not_flushed_when_an_error_happens(stdout, service_name, log_level, monkeypatch):
132-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
134+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
133135

134136
# GIVEN A logger configured with a buffer and error flushing disabled
135137
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG", flush_on_error=False)
@@ -157,10 +159,10 @@ def test_logger_buffer_is_not_flushed_when_an_error_happens(stdout, service_name
157159

158160

159161
def test_create_and_flush_logs(stdout, service_name, monkeypatch):
160-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
162+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
161163

162164
# GIVEN A logger configured with a large buffer
163-
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG", flush_on_error=True)
165+
logger_buffer_config = LoggerBufferConfig(max_size=10240)
164166
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
165167

166168
# WHEN Logging a message and then flushing the buffer
@@ -172,12 +174,27 @@ def test_create_and_flush_logs(stdout, service_name, monkeypatch):
172174
assert "this log line will be flushed" == log[0]["message"]
173175

174176

177+
def test_ensure_log_location_after_flush_buffer(stdout, service_name, monkeypatch):
178+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
179+
180+
# GIVEN A logger configured with a sufficiently large buffer
181+
logger_buffer_config = LoggerBufferConfig(max_size=10240)
182+
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
183+
184+
# WHEN Logging a debug message and immediately flushing the buffer
185+
logger.debug("this log line will be flushed")
186+
logger.flush_buffer()
187+
188+
# THEN Validate that the log location is precisely captured
189+
log = capture_multiple_logging_statements_output(stdout)
190+
assert "test_ensure_log_location_after_flush_buffer:184" in log[0]["location"]
191+
192+
175193
def test_create_buffer_with_items_evicted(stdout, service_name, monkeypatch):
176-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
194+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
177195

178196
# GIVEN A logger configured with a 1024-byte buffer
179197
logger_buffer_config = LoggerBufferConfig(max_size=1024, minimum_log_level="DEBUG")
180-
181198
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
182199

183200
# WHEN Adding multiple log entries that exceed buffer size
@@ -193,11 +210,10 @@ def test_create_buffer_with_items_evicted(stdout, service_name, monkeypatch):
193210

194211

195212
def test_create_buffer_with_items_evicted_with_next_invocation(stdout, service_name, monkeypatch):
196-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
213+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
197214

198215
# GIVEN A logger configured with a 1024-byte buffer
199216
logger_buffer_config = LoggerBufferConfig(max_size=1024, minimum_log_level="DEBUG")
200-
201217
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
202218

203219
# WHEN Adding multiple log entries that exceed buffer size
@@ -224,7 +240,7 @@ def test_create_buffer_with_items_evicted_with_next_invocation(stdout, service_n
224240

225241

226242
def test_flush_buffer_when_empty(stdout, service_name, monkeypatch):
227-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
243+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
228244

229245
# GIVEN: A logger configured with a 1024-byte buffer
230246
logger_buffer_config = LoggerBufferConfig(max_size=1024, minimum_log_level="DEBUG")
@@ -240,7 +256,7 @@ def test_flush_buffer_when_empty(stdout, service_name, monkeypatch):
240256

241257

242258
def test_log_record_exceeding_buffer_size(stdout, service_name, monkeypatch):
243-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
259+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
244260

245261
# GIVEN A logger configured with a small 10-byte buffer
246262
logger_buffer_config = LoggerBufferConfig(max_size=10, minimum_log_level="DEBUG")
@@ -254,7 +270,9 @@ def test_log_record_exceeding_buffer_size(stdout, service_name, monkeypatch):
254270

255271

256272
@pytest.mark.parametrize("log_level", ["WARNING", "INFO"])
257-
def test_logger_buffer_log_output_for_levels_above_minimum(log_level, stdout, service_name):
273+
def test_logger_buffer_log_output_for_levels_above_minimum(log_level, stdout, service_name, monkeypatch):
274+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
275+
258276
# GIVEN A logger configured with a buffer and minimum log level set to DEBUG
259277
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG")
260278
logger = Logger(level=log_level, service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
@@ -276,10 +294,10 @@ def test_logger_buffer_log_output_for_levels_above_minimum(log_level, stdout, se
276294

277295

278296
def test_logger_buffer_flush_on_uncaught_exception(stdout, service_name, monkeypatch, lambda_context):
279-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
297+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
280298

281299
# GIVEN: A logger configured with a large buffer and error-based flushing
282-
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG", flush_on_error=True)
300+
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG")
283301
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
284302

285303
@logger.inject_lambda_context(flush_buffer_on_uncaught_error=True)
@@ -301,10 +319,10 @@ def handler(event, context):
301319

302320

303321
def test_logger_buffer_not_flush_on_uncaught_exception(stdout, service_name, monkeypatch, lambda_context):
304-
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
322+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
305323

306324
# GIVEN: A logger configured with a large buffer and error-based flushing
307-
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG", flush_on_error=True)
325+
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG")
308326
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
309327

310328
@logger.inject_lambda_context(flush_buffer_on_uncaught_error=False)
@@ -321,3 +339,50 @@ def handler(event, context):
321339
# THEN Verify that buffered log messages are flushed before the exception
322340
log = capture_multiple_logging_statements_output(stdout)
323341
assert len(log) == 0
342+
343+
344+
def test_buffer_configuration_propagation_across_logger_instances(stdout, service_name, monkeypatch):
345+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
346+
347+
# GIVEN A logger configured with specific buffer settings
348+
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG")
349+
350+
# Create primary logger with explicit buffer configuration
351+
primary_logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
352+
353+
# Create secondary logger for the same service (should inherit buffer config)
354+
secondary_logger = Logger(level="DEBUG", service=service_name)
355+
356+
# WHEN Logging messages and flushing the buffer
357+
primary_logger.debug("Log message from primary logger")
358+
secondary_logger.debug("Log message from secondary logger")
359+
primary_logger.flush_buffer()
360+
361+
# THEN Verify log messages are correctly captured and output
362+
log = capture_multiple_logging_statements_output(stdout)
363+
364+
assert "Log message from primary logger" == log[0]["message"]
365+
assert "Log message from secondary logger" == log[1]["message"]
366+
assert primary_logger._logger.powertools_buffer_config == secondary_logger._logger.powertools_buffer_config
367+
368+
369+
def test_logger_buffer_is_cleared_between_lambda_invocations(stdout, service_name, monkeypatch, lambda_context):
370+
# Set initial trace ID for first Lambda invocation
371+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
372+
373+
# GIVEN A logger configured with specific buffer parameters
374+
logger_buffer_config = LoggerBufferConfig(max_size=10240)
375+
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
376+
377+
def handler(event, context):
378+
logger.debug("debug line")
379+
380+
# WHEN First Lambda invocation with initial trace ID
381+
handler({}, lambda_context)
382+
383+
# WHEN New Lambda invocation arrives with different trace ID
384+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "2-ABC39786-5908a82a246fb67f3089263f")
385+
handler({}, lambda_context)
386+
387+
# THEN Verify buffer for the original trace ID is cleared
388+
assert not logger.buffer_cache.get("1-67c39786-5908a82a246fb67f3089263f")

0 commit comments

Comments
 (0)