Skip to content

Commit 0d63cd4

Browse files
committed
Merge branch 'fix/existing-loggers-duplicated-logs' of https://github.com/mploski/aws-lambda-powertools-python into fix/existing-loggers-duplicated-logs
* 'fix/existing-loggers-duplicated-logs' of https://github.com/mploski/aws-lambda-powertools-python: Add child loggers validation test Ensure external loggers doesnt propagate logs
2 parents 565d0a5 + 3d3bfc8 commit 0d63cd4

File tree

2 files changed

+91
-5
lines changed

2 files changed

+91
-5
lines changed

aws_lambda_powertools/logging/utils.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def copy_config_to_registered_loggers(
2424
exclude : Optional[Set[str]], optional
2525
List of logger names to exclude, by default None
2626
"""
27-
27+
package_logger = logging.getLogger("aws_lambda_powertools")
2828
level = log_level or source_logger.level
2929

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

37-
# Exclude source logger by default
37+
# Exclude source and powertools package logger by default
3838
if exclude:
39-
exclude.add(source_logger.name)
39+
exclude.update(source_logger.name, package_logger.name)
4040
else:
41-
exclude = {source_logger.name}
41+
exclude = {source_logger.name, package_logger.name}
4242

4343
# Prepare loggers set
4444
if include:
@@ -75,6 +75,7 @@ def _find_registered_loggers(
7575
def _configure_logger(source_logger: Logger, logger: logging.Logger, level: Union[int, str]) -> None:
7676
logger.handlers = []
7777
logger.setLevel(level)
78+
logger.propagate = False
7879
source_logger.debug(f"Logger {logger} reconfigured to use logging level {level}")
7980
for source_handler in source_logger.handlers:
8081
logger.addHandler(source_handler)

tests/functional/test_logger_utils.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from enum import Enum
77

88
import pytest
9+
from pytest_mock import MockerFixture
910

1011
from aws_lambda_powertools import Logger
1112
from aws_lambda_powertools.logging import formatter, utils
@@ -184,7 +185,7 @@ def test_copy_config_to_ext_loggers_custom_log_level(stdout, logger, log_level):
184185
assert log["level"] == log_level.WARNING.name
185186

186187

187-
def test_copy_config_to_ext_loggers_should_not_break_append_keys(stdout, logger, log_level):
188+
def test_copy_config_to_ext_loggers_should_not_break_append_keys(stdout, log_level):
188189
# GIVEN powertools logger initialized
189190
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
190191

@@ -193,3 +194,87 @@ def test_copy_config_to_ext_loggers_should_not_break_append_keys(stdout, logger,
193194

194195
# THEN append_keys should not raise an exception
195196
powertools_logger.append_keys(key="value")
197+
198+
199+
def test_copy_config_to_ext_loggers_child_loggers_append_before_work(stdout):
200+
# GIVEN powertools logger AND child initialized AND
201+
202+
# GIVEN Loggers are initialized
203+
# create child logger before parent to mimick
204+
# importing logger from another module/file
205+
# as loggers are created in global scope
206+
service = service_name()
207+
child = Logger(stream=stdout, service=service, child=True)
208+
parent = Logger(stream=stdout, service=service)
209+
210+
# WHEN a child Logger adds an additional key AND parent logger adds additional key
211+
child.structure_logs(append=True, customer_id="value")
212+
parent.structure_logs(append=True, user_id="value")
213+
# WHEN configuration copied from powertools logger
214+
# AND powertools logger and child logger used
215+
utils.copy_config_to_registered_loggers(source_logger=parent)
216+
parent.warning("Logger message")
217+
child.warning("Child logger message")
218+
219+
# THEN payment_id key added to both powertools logger and child logger
220+
parent_log, child_log = capture_multiple_logging_statements_output(stdout)
221+
assert "customer_id" in parent_log
222+
assert "customer_id" in child_log
223+
assert "user_id" in parent_log
224+
assert "user_id" in child_log
225+
assert child.parent.name == service
226+
227+
228+
def test_copy_config_to_ext_loggers_child_loggers_append_after_works(stdout):
229+
# GIVEN powertools logger AND child initialized AND
230+
231+
# GIVEN Loggers are initialized
232+
# create child logger before parent to mimick
233+
# importing logger from another module/file
234+
# as loggers are created in global scope
235+
service = service_name()
236+
child = Logger(stream=stdout, service=service, child=True)
237+
parent = Logger(stream=stdout, service=service)
238+
239+
# WHEN a child Logger adds an additional key AND parent logger adds additional key
240+
# AND configuration copied from powertools logger
241+
# AND powertools logger and child logger used
242+
utils.copy_config_to_registered_loggers(source_logger=parent)
243+
child.structure_logs(append=True, customer_id="value")
244+
parent.structure_logs(append=True, user_id="value")
245+
parent.warning("Logger message")
246+
child.warning("Child logger message")
247+
248+
# THEN payment_id key added to both powertools logger and child logger
249+
parent_log, child_log = capture_multiple_logging_statements_output(stdout)
250+
assert "customer_id" in parent_log
251+
assert "customer_id" in child_log
252+
assert "user_id" in parent_log
253+
assert "user_id" in child_log
254+
assert child.parent.name == service
255+
256+
257+
def test_copy_config_to_ext_loggers_no_duplicate_logs(stdout, logger, log_level):
258+
# GIVEN an root logger, external logger and powertools logger initialized
259+
260+
root_logger = logging.getLogger()
261+
handler = logging.StreamHandler(stdout)
262+
formatter = logging.Formatter('{"message": "%(message)s"}')
263+
handler.setFormatter(formatter)
264+
root_logger.handlers = [handler]
265+
266+
logger = logger()
267+
268+
powertools_logger = Logger(service=service_name(), level=log_level.CRITICAL.value, stream=stdout)
269+
level = log_level.WARNING.name
270+
271+
# WHEN configuration copied from powertools logger
272+
# AND external logger used with custom log_level
273+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include={logger.name}, log_level=level)
274+
msg = "test message4"
275+
logger.warning(msg)
276+
277+
# THEN no root logger logs AND log is not duplicated
278+
logs = capture_multiple_logging_statements_output(stdout)
279+
assert not {"message": msg} in logs
280+
assert sum(msg in log.values() for log in logs) == 1

0 commit comments

Comments
 (0)