Skip to content

Commit 694d68f

Browse files
authored
Merge pull request #1075 from mploski/fix/existing-loggers-duplicated-logs
fix(logger): Ensure external loggers doesn't propagate logs
2 parents 565d0a5 + 3c4df97 commit 694d68f

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

Diff for: aws_lambda_powertools/logging/utils.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from .logger import Logger
55

6+
PACKAGE_LOGGER = "aws_lambda_powertools"
7+
68

79
def copy_config_to_registered_loggers(
810
source_logger: Logger,
@@ -24,7 +26,6 @@ def copy_config_to_registered_loggers(
2426
exclude : Optional[Set[str]], optional
2527
List of logger names to exclude, by default None
2628
"""
27-
2829
level = log_level or source_logger.level
2930

3031
# Assumptions: Only take parent loggers not children (dot notation rule)
@@ -34,11 +35,15 @@ def copy_config_to_registered_loggers(
3435
# 3. Include and exclude set? Add Logger if it’s in include and not in exclude
3536
# 4. Only exclude set? Ignore Logger in the excluding list
3637

37-
# Exclude source logger by default
38+
# Exclude source and powertools package logger by default
39+
# If source logger is a child ensure we exclude parent logger to not break child logger
40+
# from receiving/pushing updates to keys being added/removed
41+
source_logger_name = source_logger.name.split(".")[0]
42+
3843
if exclude:
39-
exclude.add(source_logger.name)
44+
exclude.update(source_logger_name, PACKAGE_LOGGER)
4045
else:
41-
exclude = {source_logger.name}
46+
exclude = {source_logger_name, PACKAGE_LOGGER}
4247

4348
# Prepare loggers set
4449
if include:
@@ -75,6 +80,7 @@ def _find_registered_loggers(
7580
def _configure_logger(source_logger: Logger, logger: logging.Logger, level: Union[int, str]) -> None:
7681
logger.handlers = []
7782
logger.setLevel(level)
83+
logger.propagate = False # ensure we don't propagate logs to existing loggers, #1073
7884
source_logger.debug(f"Logger {logger} reconfigured to use logging level {level}")
7985
for source_handler in source_logger.handlers:
8086
logger.addHandler(source_handler)

Diff for: tests/functional/test_logger_utils.py

+49-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def test_copy_config_to_ext_loggers_custom_log_level(stdout, logger, log_level):
184184
assert log["level"] == log_level.WARNING.name
185185

186186

187-
def test_copy_config_to_ext_loggers_should_not_break_append_keys(stdout, logger, log_level):
187+
def test_copy_config_to_ext_loggers_should_not_break_append_keys(stdout, log_level):
188188
# GIVEN powertools logger initialized
189189
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
190190

@@ -193,3 +193,51 @@ def test_copy_config_to_ext_loggers_should_not_break_append_keys(stdout, logger,
193193

194194
# THEN append_keys should not raise an exception
195195
powertools_logger.append_keys(key="value")
196+
197+
198+
def test_copy_config_to_parent_loggers_only(stdout):
199+
# GIVEN Powertools Logger and Child Logger are initialized
200+
# and Powertools Logger config is copied over
201+
service = service_name()
202+
child = Logger(stream=stdout, service=service, child=True)
203+
parent = Logger(stream=stdout, service=service)
204+
utils.copy_config_to_registered_loggers(source_logger=parent)
205+
206+
# WHEN either parent or child logger append keys
207+
child.append_keys(customer_id="value")
208+
parent.append_keys(user_id="value")
209+
parent.info("Logger message")
210+
child.info("Child logger message")
211+
212+
# THEN both custom keys should be propagated bi-directionally in parent and child loggers
213+
# as child logger won't be touched when config is being copied
214+
parent_log, child_log = capture_multiple_logging_statements_output(stdout)
215+
assert "customer_id" in parent_log, child_log
216+
assert "user_id" in parent_log, child_log
217+
assert child.parent.name == service
218+
219+
220+
def test_copy_config_to_ext_loggers_no_duplicate_logs(stdout, logger, log_level):
221+
# GIVEN an root logger, external logger and powertools logger initialized
222+
223+
root_logger = logging.getLogger()
224+
handler = logging.StreamHandler(stdout)
225+
formatter = logging.Formatter('{"message": "%(message)s"}')
226+
handler.setFormatter(formatter)
227+
root_logger.addHandler(handler)
228+
229+
logger = logger()
230+
231+
powertools_logger = Logger(service=service_name(), level=log_level.CRITICAL.value, stream=stdout)
232+
level = log_level.WARNING.name
233+
234+
# WHEN configuration copied from powertools logger
235+
# AND external logger used with custom log_level
236+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include={logger.name}, log_level=level)
237+
msg = "test message4"
238+
logger.warning(msg)
239+
240+
# THEN no root logger logs AND log is not duplicated
241+
logs = capture_multiple_logging_statements_output(stdout)
242+
assert {"message": msg} not in logs
243+
assert sum(msg in log.values() for log in logs) == 1

0 commit comments

Comments
 (0)