Skip to content

Commit a659301

Browse files
author
Michal Ploski
committed
feat(logger): enable powertools logging for imported libraries (#40)
1 parent 321f493 commit a659301

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import logging
2+
from typing import List, Optional, TypeVar
3+
4+
from .logger import Logger
5+
6+
PowertoolsLogger = TypeVar("PowertoolsLogger", bound=Logger)
7+
8+
9+
def copy_config_to_registered_loggers(
10+
source_logger: PowertoolsLogger, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None
11+
):
12+
root_loggers = [
13+
logging.getLogger(name)
14+
for name in logging.root.manager.loggerDict
15+
if "." not in name and name != source_logger.name
16+
]
17+
source_logger.debug(f"Found registered root loggers: {root_loggers}")
18+
if include and not exclude:
19+
root_loggers = [logger for logger in root_loggers if logger.name in include]
20+
elif not include and exclude:
21+
root_loggers = [logger for logger in root_loggers if logger.name not in exclude]
22+
elif include and exclude:
23+
root_loggers = [logger for logger in root_loggers if logger.name in include and logger.name not in exclude]
24+
25+
source_logger.debug(f"Filtered root loggers: {root_loggers}")
26+
for logger in root_loggers:
27+
logger.handlers = []
28+
logger.setLevel(source_logger.level)
29+
for source_handler in source_logger.handlers:
30+
logger.addHandler(source_handler)

tests/functional/test_logger_utils.py

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import io
2+
import json
3+
import logging
4+
import random
5+
import string
6+
from enum import Enum
7+
8+
import pytest
9+
10+
from aws_lambda_powertools import Logger
11+
from aws_lambda_powertools.logging import formatter, utils
12+
13+
14+
@pytest.fixture
15+
def stdout():
16+
return io.StringIO()
17+
18+
19+
@pytest.fixture
20+
def log_level():
21+
class LogLevel(Enum):
22+
NOTSET = 0
23+
INFO = 20
24+
25+
return LogLevel
26+
27+
28+
def capture_logging_output(stdout):
29+
return json.loads(stdout.getvalue().strip())
30+
31+
32+
def service_name():
33+
chars = string.ascii_letters + string.digits
34+
return "".join(random.SystemRandom().choice(chars) for _ in range(15))
35+
36+
37+
@pytest.fixture
38+
def logger(stdout, log_level):
39+
def _logger():
40+
logging.basicConfig(stream=stdout, level=log_level.NOTSET.value)
41+
logger = logging.getLogger(name=service_name())
42+
return logger
43+
44+
return _logger
45+
46+
47+
def test_copy_config_to_ext_loggers(stdout, logger, log_level):
48+
49+
msg = "test message"
50+
51+
# GIVEN a external logger and powertools logger initialized
52+
logger = logger()
53+
logger_initial_handlers = logger.handlers.copy()
54+
logger_initial_level = logger.level
55+
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
56+
57+
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
58+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger)
59+
logger.info(msg)
60+
log = capture_logging_output(stdout)
61+
62+
# THEN
63+
assert not logger_initial_handlers
64+
assert logger_initial_level == log_level.NOTSET.value
65+
assert len(logger.handlers) == 1
66+
assert type(logger.handlers[0]) is logging.StreamHandler
67+
assert type(logger.handlers[0].formatter) is formatter.LambdaPowertoolsFormatter
68+
assert logger.level == log_level.INFO.value
69+
assert log["message"] == msg
70+
assert log["level"] == log_level.INFO.name
71+
72+
73+
def test_copy_config_to_ext_loggers_include(stdout, logger, log_level):
74+
75+
msg = "test message"
76+
77+
# GIVEN a external logger and powertools logger initialized
78+
logger = logger()
79+
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
80+
81+
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
82+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include=[logger.name])
83+
logger.info(msg)
84+
log = capture_logging_output(stdout)
85+
86+
# THEN
87+
assert len(logger.handlers) == 1
88+
assert type(logger.handlers[0]) is logging.StreamHandler
89+
assert type(logger.handlers[0].formatter) is formatter.LambdaPowertoolsFormatter
90+
assert logger.level == log_level.INFO.value
91+
assert log["message"] == msg
92+
assert log["level"] == log_level.INFO.name
93+
94+
95+
def test_copy_config_to_ext_loggers_wrong_include(stdout, logger, log_level):
96+
97+
# GIVEN a external logger and powertools logger initialized
98+
logger = logger()
99+
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
100+
101+
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
102+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include=["non-existing-logger"])
103+
104+
# THEN
105+
assert not logger.handlers
106+
107+
108+
def test_copy_config_to_ext_loggers_exclude(stdout, logger, log_level):
109+
110+
# GIVEN a external logger and powertools logger initialized
111+
logger = logger()
112+
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
113+
114+
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
115+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, exclude=[logger.name])
116+
117+
# THEN
118+
assert not logger.handlers
119+
120+
121+
def test_copy_config_to_ext_loggers_include_exclude(stdout, logger, log_level):
122+
123+
msg = "test message"
124+
125+
# GIVEN a external logger and powertools logger initialized
126+
logger_1 = logger()
127+
logger_2 = logger()
128+
129+
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
130+
131+
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
132+
utils.copy_config_to_registered_loggers(
133+
source_logger=powertools_logger, include=[logger_1.name, logger_2.name], exclude=[logger_1.name]
134+
)
135+
logger_2.info(msg)
136+
log = capture_logging_output(stdout)
137+
# THEN
138+
assert not logger_1.handlers
139+
assert len(logger_2.handlers) == 1
140+
assert type(logger_2.handlers[0]) is logging.StreamHandler
141+
assert type(logger_2.handlers[0].formatter) is formatter.LambdaPowertoolsFormatter
142+
assert logger_2.level == log_level.INFO.value
143+
assert log["message"] == msg
144+
assert log["level"] == log_level.INFO.name

0 commit comments

Comments
 (0)