Skip to content

Commit 025e106

Browse files
committed
test(logging): add unit/functional tests for buffering handler and logger configuration
1 parent 0d80d0b commit 025e106

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

tests/functional/logger/required_dependencies/test_logger_utils.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
import random
77
import string
88
from enum import Enum
9+
from unittest.mock import patch
910

1011
import pytest
1112

1213
from aws_lambda_powertools import Logger
1314
from aws_lambda_powertools.logging import formatter, utils
15+
from aws_lambda_powertools.logging.buffer import LoggerBufferConfig
16+
from aws_lambda_powertools.logging.buffer.handler import BufferingHandler
1417

1518

1619
@pytest.fixture
@@ -315,3 +318,84 @@ def test_copy_config_to_ext_loggers_but_preserve_log_levels(stdout, logger, log_
315318
# THEN external loggers log levels should be preserved
316319
assert logger_1.level != powertools_logger.log_level
317320
assert logger_2.level != powertools_logger.log_level
321+
322+
323+
def test_copy_config_with_buffering_uses_buffering_handler(stdout, logger, log_level):
324+
# GIVEN an external logger and Powertools logger with buffer_config
325+
ext_logger = logger()
326+
buffer_config = LoggerBufferConfig(max_bytes=10240)
327+
powertools_logger = Logger(
328+
service=service_name(),
329+
level=log_level.INFO.value,
330+
stream=stdout,
331+
buffer_config=buffer_config,
332+
)
333+
334+
# WHEN configuration is copied with include_buffering=True
335+
utils.copy_config_to_registered_loggers(
336+
source_logger=powertools_logger,
337+
include_buffering=True,
338+
include={ext_logger.name},
339+
)
340+
341+
# THEN external logger has exactly one handler and it is BufferingHandler (not StreamHandler)
342+
assert len(ext_logger.handlers) == 1
343+
assert isinstance(ext_logger.handlers[0], BufferingHandler)
344+
assert not isinstance(ext_logger.handlers[0], logging.StreamHandler)
345+
346+
# AND when external logger emits a log with mocked tracer_id, it is buffered
347+
with patch("aws_lambda_powertools.logging.logger.get_tracer_id", return_value="test-trace-id"):
348+
ext_logger.info("Test message from external logger")
349+
output = stdout.getvalue()
350+
assert "Test message from external logger" not in output
351+
352+
# AND when buffer is flushed, the log appears
353+
powertools_logger.flush_buffer()
354+
output = stdout.getvalue()
355+
assert "Test message from external logger" in output
356+
357+
358+
def test_copy_config_buffering_without_config_uses_normal_handlers(stdout, logger, log_level):
359+
# GIVEN an external logger and Powertools logger WITHOUT buffer_config
360+
ext_logger = logger()
361+
powertools_logger = Logger(
362+
service=service_name(),
363+
level=log_level.INFO.value,
364+
stream=stdout,
365+
)
366+
367+
# WHEN configuration is copied with include_buffering=True
368+
utils.copy_config_to_registered_loggers(
369+
source_logger=powertools_logger,
370+
include_buffering=True,
371+
include={ext_logger.name},
372+
)
373+
374+
# THEN external logger gets normal Powertools handler (no BufferingHandler)
375+
assert len(ext_logger.handlers) == 1
376+
assert isinstance(ext_logger.handlers[0], logging.StreamHandler)
377+
assert isinstance(ext_logger.handlers[0].formatter, formatter.LambdaPowertoolsFormatter)
378+
assert not isinstance(ext_logger.handlers[0], BufferingHandler)
379+
380+
381+
def test_copy_config_to_ext_loggers_default_include_buffering_false(stdout, logger, log_level):
382+
# GIVEN Powertools logger with buffer_config and external logger
383+
ext_logger = logger()
384+
buffer_config = LoggerBufferConfig(max_bytes=10240)
385+
powertools_logger = Logger(
386+
service=service_name(),
387+
level=log_level.INFO.value,
388+
stream=stdout,
389+
buffer_config=buffer_config,
390+
)
391+
392+
# WHEN configuration is copied without include_buffering (default False)
393+
utils.copy_config_to_registered_loggers(
394+
source_logger=powertools_logger,
395+
include={ext_logger.name},
396+
)
397+
398+
# THEN external logger gets normal StreamHandler, not BufferingHandler
399+
assert len(ext_logger.handlers) == 1
400+
assert isinstance(ext_logger.handlers[0], logging.StreamHandler)
401+
assert not isinstance(ext_logger.handlers[0], BufferingHandler)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from __future__ import annotations
2+
3+
import io
4+
import json
5+
import logging
6+
7+
from aws_lambda_powertools import Logger
8+
from aws_lambda_powertools.logging.buffer.config import LoggerBufferConfig
9+
from aws_lambda_powertools.logging.buffer.handler import BufferingHandler
10+
from aws_lambda_powertools.shared import constants
11+
12+
13+
def test_buffering_handler_init_stores_dependencies():
14+
# GIVEN real buffer_config, source_logger (Logger with buffer), and its buffer_cache
15+
buffer_config = LoggerBufferConfig(max_bytes=10240)
16+
source_logger = Logger(service="test1", buffer_config=buffer_config, stream=io.StringIO())
17+
buffer_cache = source_logger._buffer_cache
18+
19+
# WHEN BufferingHandler is initialized
20+
handler = BufferingHandler(
21+
buffer_cache=buffer_cache,
22+
buffer_config=buffer_config,
23+
source_logger=source_logger,
24+
)
25+
26+
# THEN all dependencies are stored on the instance
27+
assert handler.buffer_cache is buffer_cache
28+
assert handler.buffer_config is buffer_config
29+
assert handler.source_logger is source_logger
30+
assert handler.level == logging.NOTSET
31+
32+
33+
def test_buffering_handler_emit_calls_add_log_record_to_buffer(monkeypatch):
34+
# GIVEN a real Logger with buffer and a BufferingHandler (tracer id set so records are buffered)
35+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1-67c39786-5908a82a246fb67f3089263f")
36+
stream = io.StringIO()
37+
buffer_config = LoggerBufferConfig(max_bytes=10240)
38+
source_logger = Logger(service="test2", buffer_config=buffer_config, stream=stream)
39+
handler = BufferingHandler(
40+
buffer_cache=source_logger._buffer_cache,
41+
buffer_config=source_logger._buffer_config,
42+
source_logger=source_logger,
43+
)
44+
record = logging.LogRecord(
45+
name="external",
46+
level=logging.INFO,
47+
pathname="",
48+
lineno=0,
49+
msg="test %s",
50+
args=("arg",),
51+
exc_info=None,
52+
func=None,
53+
)
54+
record.stack_info = None
55+
56+
# WHEN the handler emits the record and the buffer is flushed
57+
handler.emit(record)
58+
source_logger.flush_buffer()
59+
60+
# THEN the buffered message appears in the logger output
61+
output = stream.getvalue()
62+
log_line = json.loads(output.strip())
63+
assert log_line["message"] == "test arg"

0 commit comments

Comments
 (0)