Skip to content

Commit 828fec4

Browse files
authored
Merge pull request #198 from awslabs/improv/suppress-logger-propagation
improv: add log filter in root handler to prevent child log records duplication
2 parents b25b2aa + ae2a539 commit 828fec4

File tree

3 files changed

+32
-15
lines changed

3 files changed

+32
-15
lines changed

Diff for: aws_lambda_powertools/logging/filters.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import logging
2+
3+
4+
class SuppressFilter(logging.Filter):
5+
def __init__(self, logger):
6+
self.logger = logger
7+
8+
def filter(self, record): # noqa: A003
9+
"""Suppress Log Records from registered logger
10+
11+
It rejects log records from registered logger e.g. a child logger
12+
otherwise it honours log propagation from any log record
13+
created by loggers who don't have a handler.
14+
"""
15+
logger = record.name
16+
return False if self.logger in logger else True

Diff for: aws_lambda_powertools/logging/logger.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Callable, Dict, Union
99

1010
from .exceptions import InvalidLoggerSamplingRateError
11+
from .filters import SuppressFilter
1112
from .formatter import JsonFormatter
1213
from .lambda_context import build_lambda_context_model
1314

@@ -147,13 +148,6 @@ def _get_logger(self):
147148
def _init_logger(self, **kwargs):
148149
"""Configures new logger"""
149150

150-
# Lambda by default configures the root logger handler
151-
# therefore, we need to remove it to prevent messages being logged twice
152-
# when customers use our Logger
153-
logger.debug("Removing Lambda root handler whether it exists")
154-
root_logger = logging.getLogger()
155-
root_logger.handlers.clear()
156-
157151
# Skip configuration if it's a child logger to prevent
158152
# multiple handlers being attached as well as different sampling mechanisms
159153
# and multiple messages from being logged as handlers can be duplicated
@@ -163,6 +157,13 @@ def _init_logger(self, **kwargs):
163157
self._logger.addHandler(self._handler)
164158
self.structure_logs(**kwargs)
165159

160+
logger.debug("Adding filter in root logger to suppress child logger records to bubble up")
161+
for handler in logging.root.handlers:
162+
# It'll add a filter to suppress any child logger from self.service
163+
# Where service is Order, it'll reject parent logger Order,
164+
# and child loggers such as Order.checkout, Order.shared
165+
handler.addFilter(SuppressFilter(self.service))
166+
166167
def _configure_sampling(self):
167168
"""Dynamically set log level based on sampling rate
168169

Diff for: tests/functional/test_logger.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -361,18 +361,18 @@ def test_logger_record_caller_location(stdout):
361361
assert caller_fn_name in log["location"]
362362

363363

364-
def test_logger_do_not_log_twice(stdout):
364+
def test_logger_do_not_log_twice_when_root_logger_is_setup(stdout):
365365
# GIVEN Lambda configures the root logger with a handler
366-
logging.basicConfig(format="%(asctime)-15s %(clientip)s %(user)-8s %(message)s", level="INFO")
367366
root_logger = logging.getLogger()
368367
root_logger.addHandler(logging.StreamHandler(stream=stdout))
369368

370-
# WHEN we create a new Logger
369+
# WHEN we create a new Logger and child Logger
371370
logger = Logger(stream=stdout)
371+
child_logger = Logger(child=True, stream=stdout)
372372
logger.info("hello")
373+
child_logger.info("hello again")
373374

374-
# THEN it should fail to unpack because root logger handler
375-
# should be removed as part of our Logger initialization
376-
assert not root_logger.handlers
377-
with pytest.raises(ValueError, match=r".*expected 2, got 1.*"):
378-
[log_one, log_two] = stdout.getvalue().strip().split("\n")
375+
# THEN it should only contain only two log entries
376+
# since child's log records propagated to root logger should be rejected
377+
logs = list(stdout.getvalue().strip().split("\n"))
378+
assert len(logs) == 2

0 commit comments

Comments
 (0)