Skip to content

Commit 36420a2

Browse files
author
Michal Ploski
committed
Add custom log level atrribute
1 parent 8059b58 commit 36420a2

File tree

2 files changed

+74
-31
lines changed

2 files changed

+74
-31
lines changed
Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,81 @@
11
import logging
2-
from typing import Callable, List, Optional, TypeVar
2+
from typing import Callable, List, Optional, Set, Union
33

44
from .logger import Logger
55

6-
PowertoolsLogger = TypeVar("PowertoolsLogger", bound=Logger)
7-
86

97
def copy_config_to_registered_loggers(
10-
source_logger: PowertoolsLogger,
11-
exclude: Optional[List[str]] = None,
12-
include: Optional[List[str]] = None,
8+
source_logger: Logger,
9+
log_level: Optional[str] = None,
10+
exclude: Optional[Set[str]] = None,
11+
include: Optional[Set[str]] = None,
1312
) -> None:
14-
"""Enable powertools logging for imported libraries.
1513

16-
Attach source logger handlers to external loggers.
17-
Modify logger level based on source logger attribute.
18-
Ensure powertools logger itself is excluded from registered list.
14+
"""Copies source Logger level and handler to all registered loggers for consistent formatting.
15+
16+
Parameters
17+
----------
18+
source_logger : Logger
19+
Powertools Logger to copy configuration from
20+
log_level : str, optional
21+
Logging level to set to registered loggers, by default uses source_logger logging level
22+
include : Optional[Set[str]], optional
23+
List of logger names to include, by default all registered loggers are included
24+
exclude : Optional[Set[str]], optional
25+
List of logger names to exclude, by default None
1926
"""
2027

21-
if include and not exclude:
22-
loggers = include
23-
filter_func = _include_registered_loggers_filter
24-
elif include and exclude:
25-
exclude = [source_logger.name, *exclude]
26-
loggers = list(set(include) - set(exclude))
28+
level = log_level or source_logger.level
29+
30+
# Assumptions: Only take parent loggers not children (dot notation rule)
31+
# Steps:
32+
# 1. Default operation: Include all registered loggers
33+
# 2. Only include set? Only add Loggers in the list and ignore all else
34+
# 3. Include and exclude set? Add Logger if it’s in include and not in exclude
35+
# 4. Only exclude set? Ignore Logger in the excluding list
36+
37+
# Exclude source logger by default
38+
if exclude:
39+
exclude.add(source_logger.name)
40+
else:
41+
exclude = set(source_logger.name)
42+
43+
# Prepare loggers set
44+
if include:
45+
loggers = include.difference(exclude)
2746
filter_func = _include_registered_loggers_filter
28-
elif not include and exclude:
29-
loggers = [source_logger.name, *exclude]
30-
filter_func = _exclude_registered_loggers_filter
3147
else:
32-
loggers = [source_logger.name]
48+
loggers = exclude
3349
filter_func = _exclude_registered_loggers_filter
3450

3551
registered_loggers = _find_registered_loggers(source_logger, loggers, filter_func)
3652
for logger in registered_loggers:
37-
_configure_logger(source_logger, logger)
53+
_configure_logger(source_logger, logger, level)
3854

3955

40-
def _include_registered_loggers_filter(loggers: List[str]):
56+
def _include_registered_loggers_filter(loggers: Set[str]):
4157
return [logging.getLogger(name) for name in logging.root.manager.loggerDict if "." not in name and name in loggers]
4258

4359

44-
def _exclude_registered_loggers_filter(loggers: List[str]) -> List[logging.Logger]:
60+
def _exclude_registered_loggers_filter(loggers: Set[str]) -> List[logging.Logger]:
4561
return [
4662
logging.getLogger(name) for name in logging.root.manager.loggerDict if "." not in name and name not in loggers
4763
]
4864

4965

5066
def _find_registered_loggers(
51-
source_logger: PowertoolsLogger, loggers: List[str], filter_func: Callable
67+
source_logger: Logger, loggers: Set[str], filter_func: Callable[[Set[str]], List[logging.Logger]]
5268
) -> List[logging.Logger]:
5369
"""Filter root loggers based on provided parameters."""
5470
root_loggers = filter_func(loggers)
5571
source_logger.debug(f"Filtered root loggers: {root_loggers}")
5672
return root_loggers
5773

5874

59-
def _configure_logger(source_logger: PowertoolsLogger, logger: logging.Logger) -> None:
75+
def _configure_logger(source_logger: Logger, logger: logging.Logger, level: Union[int, str]) -> None:
6076
logger.handlers = []
61-
logger.setLevel(source_logger.level)
62-
source_logger.debug(f"Logger {logger} reconfigured to use logging level {source_logger.level}")
77+
logger.setLevel(level)
78+
source_logger.debug(f"Logger {logger} reconfigured to use logging level {level}")
6379
for source_handler in source_logger.handlers:
6480
logger.addHandler(source_handler)
6581
source_logger.debug(f"Logger {logger} reconfigured to use {source_handler}")

tests/functional/test_logger_utils.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def log_level():
2121
class LogLevel(Enum):
2222
NOTSET = 0
2323
INFO = 20
24+
WARNING = 30
25+
CRITICAL = 50
2426

2527
return LogLevel
2628

@@ -83,7 +85,7 @@ def test_copy_config_to_ext_loggers_include(stdout, logger, log_level):
8385
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
8486

8587
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
86-
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include=[logger.name])
88+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include={logger.name})
8789
logger.info(msg)
8890
log = capture_logging_output(stdout)
8991

@@ -103,7 +105,7 @@ def test_copy_config_to_ext_loggers_wrong_include(stdout, logger, log_level):
103105
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
104106

105107
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
106-
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include=["non-existing-logger"])
108+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include={"non-existing-logger"})
107109

108110
# THEN
109111
assert not logger.handlers
@@ -116,7 +118,7 @@ def test_copy_config_to_ext_loggers_exclude(stdout, logger, log_level):
116118
powertools_logger = Logger(service=service_name(), level=log_level.INFO.value, stream=stdout)
117119

118120
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
119-
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, exclude=[logger.name])
121+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, exclude={logger.name})
120122

121123
# THEN
122124
assert not logger.handlers
@@ -134,7 +136,7 @@ def test_copy_config_to_ext_loggers_include_exclude(stdout, logger, log_level):
134136

135137
# WHEN configuration copied from powertools logger to ALL external loggers AND our external logger used
136138
utils.copy_config_to_registered_loggers(
137-
source_logger=powertools_logger, include=[logger_1.name, logger_2.name], exclude=[logger_1.name]
139+
source_logger=powertools_logger, include={logger_1.name, logger_2.name}, exclude={logger_1.name}
138140
)
139141
logger_2.info(msg)
140142
log = capture_logging_output(stdout)
@@ -164,3 +166,28 @@ def test_copy_config_to_ext_loggers_clean_old_handlers(stdout, logger, log_level
164166
assert len(logger.handlers) == 1
165167
assert type(logger.handlers[0]) is logging.StreamHandler
166168
assert type(logger.handlers[0].formatter) is formatter.LambdaPowertoolsFormatter
169+
170+
171+
def test_copy_config_to_ext_loggers_custom_log_level(stdout, logger, log_level):
172+
173+
msg = "test message"
174+
175+
# GIVEN a external logger and powertools logger initialized
176+
logger = logger()
177+
powertools_logger = Logger(service=service_name(), level=log_level.CRITICAL.value, stream=stdout)
178+
level = log_level.WARNING.name
179+
180+
# WHEN configuration copied from powertools logger to ALL external loggers
181+
# AND our external logger used with custom log_level
182+
utils.copy_config_to_registered_loggers(source_logger=powertools_logger, include={logger.name}, log_level=level)
183+
logger.warning(msg)
184+
log = capture_logging_output(stdout)
185+
186+
# THEN
187+
assert len(logger.handlers) == 1
188+
assert type(logger.handlers[0]) is logging.StreamHandler
189+
assert type(logger.handlers[0].formatter) is formatter.LambdaPowertoolsFormatter
190+
assert powertools_logger.level == log_level.CRITICAL.value
191+
assert logger.level == log_level.WARNING.value
192+
assert log["message"] == msg
193+
assert log["level"] == log_level.WARNING.name

0 commit comments

Comments
 (0)