|
6 | 6 | import os
|
7 | 7 | import random
|
8 | 8 | import sys
|
| 9 | +import warnings |
9 | 10 | from typing import (
|
10 | 11 | IO,
|
11 | 12 | TYPE_CHECKING,
|
|
24 | 25 | import jmespath
|
25 | 26 |
|
26 | 27 | from aws_lambda_powertools.logging import compat
|
27 |
| - |
28 |
| -from ..shared import constants |
29 |
| -from ..shared.functions import ( |
| 28 | +from aws_lambda_powertools.shared import constants |
| 29 | +from aws_lambda_powertools.shared.functions import ( |
30 | 30 | extract_event_from_common_models,
|
31 | 31 | resolve_env_var_choice,
|
32 | 32 | resolve_truthy_env_var_choice,
|
33 | 33 | )
|
| 34 | + |
34 | 35 | from ..shared.types import AnyCallableT
|
35 | 36 | from .exceptions import InvalidLoggerSamplingRateError
|
36 | 37 | from .filters import SuppressFilter
|
@@ -76,7 +77,7 @@ class Logger:
|
76 | 77 | ---------------------
|
77 | 78 | POWERTOOLS_SERVICE_NAME : str
|
78 | 79 | service name
|
79 |
| - LOG_LEVEL: str |
| 80 | + POWERTOOLS_LOG_LEVEL: str |
80 | 81 | logging level (e.g. INFO, DEBUG)
|
81 | 82 | POWERTOOLS_LOGGER_SAMPLE_RATE: float
|
82 | 83 | sampling rate ranging from 0 to 1, 1 being 100% sampling
|
@@ -297,7 +298,7 @@ def _init_logger(
|
297 | 298 | if self.child or is_logger_preconfigured:
|
298 | 299 | return
|
299 | 300 |
|
300 |
| - self.setLevel(self._determine_log_level(log_level)) |
| 301 | + self.setLevel(log_level) |
301 | 302 | self._configure_sampling()
|
302 | 303 | self.addHandler(self.logger_handler)
|
303 | 304 | self.structure_logs(formatter_options=formatter_options, **kwargs)
|
@@ -676,8 +677,8 @@ def get_correlation_id(self) -> Optional[str]:
|
676 | 677 | return self.registered_formatter.log_format.get("correlation_id")
|
677 | 678 | return None
|
678 | 679 |
|
679 |
| - def setLevel(self, level: Union[str, int]) -> None: |
680 |
| - return self._logger.setLevel(level) |
| 680 | + def setLevel(self, level: Union[str, int, None]) -> None: |
| 681 | + return self._logger.setLevel(self._determine_log_level(level)) |
681 | 682 |
|
682 | 683 | def addHandler(self, handler: logging.Handler) -> None:
|
683 | 684 | return self._logger.addHandler(handler)
|
@@ -714,17 +715,88 @@ def handlers(self) -> List[logging.Handler]:
|
714 | 715 | """
|
715 | 716 | return self._logger.handlers
|
716 | 717 |
|
717 |
| - @staticmethod |
718 |
| - def _determine_log_level(level: Union[str, int, None]) -> Union[str, int]: |
719 |
| - """Returns preferred log level set by the customer in upper case""" |
| 718 | + def _get_aws_lambda_log_level(self) -> Optional[str]: |
| 719 | + """ |
| 720 | + Retrieve the log level for AWS Lambda from the Advanced Logging Controls feature. |
| 721 | + Returns: |
| 722 | + Optional[str]: The corresponding logging level. |
| 723 | + """ |
| 724 | + |
| 725 | + return constants.LAMBDA_ADVANCED_LOGGING_LEVELS.get(os.getenv(constants.LAMBDA_LOG_LEVEL_ENV)) |
| 726 | + |
| 727 | + def _get_powertools_log_level(self, level: Union[str, int, None]) -> Optional[str]: |
| 728 | + """Retrieve the log level for Powertools from the environment variable or level parameter. |
| 729 | + If log level is an integer, we convert to its respective string level `logging.getLevelName()`. |
| 730 | + If no log level is provided, we check env vars for the log level: POWERTOOLS_LOG_LEVEL_ENV and POWERTOOLS_LOG_LEVEL_LEGACY_ENV. |
| 731 | + Parameters: |
| 732 | + ----------- |
| 733 | + level : Union[str, int, None] |
| 734 | + The specified log level as a string, integer, or None. |
| 735 | + Environment variables |
| 736 | + --------------------- |
| 737 | + POWERTOOLS_LOG_LEVEL : str |
| 738 | + log level (e.g: INFO, DEBUG, WARNING, ERROR, CRITICAL) |
| 739 | + LOG_LEVEL (Legacy) : str |
| 740 | + log level (e.g: INFO, DEBUG, WARNING, ERROR, CRITICAL) |
| 741 | + Returns: |
| 742 | + -------- |
| 743 | + Optional[str]: |
| 744 | + The corresponding logging level. Returns None if the log level is not explicitly specified. |
| 745 | + """ # noqa E501 |
| 746 | + |
| 747 | + # Extract log level from Powertools Logger env vars |
| 748 | + log_level_env = os.getenv(constants.POWERTOOLS_LOG_LEVEL_ENV) or os.getenv( |
| 749 | + constants.POWERTOOLS_LOG_LEVEL_LEGACY_ENV, |
| 750 | + ) |
| 751 | + # If level is an int (logging.INFO), return its respective string ("INFO") |
720 | 752 | if isinstance(level, int):
|
721 |
| - return level |
| 753 | + return logging.getLevelName(level) |
| 754 | + |
| 755 | + return level or log_level_env |
| 756 | + |
| 757 | + def _determine_log_level(self, level: Union[str, int, None]) -> Union[str, int]: |
| 758 | + """Determine the effective log level considering Lambda and Powertools preferences. |
| 759 | + It emits an UserWarning if Lambda ALC log level is lower than Logger log level. |
| 760 | + Parameters: |
| 761 | + ----------- |
| 762 | + level: Union[str, int, None] |
| 763 | + The specified log level as a string, integer, or None. |
| 764 | + Returns: |
| 765 | + ---------- |
| 766 | + Union[str, int]: The effective logging level. |
| 767 | + """ |
722 | 768 |
|
723 |
| - log_level: Optional[str] = level or os.getenv("LOG_LEVEL") |
724 |
| - if log_level is None: |
| 769 | + # This function consider the following order of precedence: |
| 770 | + # 1 - If a log level is set using AWS Lambda Advanced Logging Controls, it sets it. |
| 771 | + # 2 - If a log level is passed to the constructor, it sets it |
| 772 | + # 3 - If a log level is set via setLevel, it sets it. |
| 773 | + # 4 - If a log level is set via Powertools env variables, it sets it. |
| 774 | + # 5 - If none of the above is true, the default log level applies INFO. |
| 775 | + |
| 776 | + lambda_log_level = self._get_aws_lambda_log_level() |
| 777 | + powertools_log_level = self._get_powertools_log_level(level) |
| 778 | + |
| 779 | + if powertools_log_level and lambda_log_level: |
| 780 | + # If Powertools log level is set and higher than AWS Lambda Advanced Logging Controls, emit a warning |
| 781 | + if logging.getLevelName(lambda_log_level) > logging.getLevelName(powertools_log_level): |
| 782 | + warnings.warn( |
| 783 | + f"Current log level ({powertools_log_level}) does not match AWS Lambda Advanced Logging Controls " |
| 784 | + f"minimum log level ({lambda_log_level}). This can lead to data loss, consider adjusting them.", |
| 785 | + UserWarning, |
| 786 | + stacklevel=2, |
| 787 | + ) |
| 788 | + |
| 789 | + # AWS Lambda Advanced Logging Controls takes precedence over Powertools log level and we use this |
| 790 | + if lambda_log_level: |
| 791 | + return lambda_log_level |
| 792 | + |
| 793 | + # Check if Powertools log level is None, which means it's not set |
| 794 | + # We assume INFO as the default log level |
| 795 | + if powertools_log_level is None: |
725 | 796 | return logging.INFO
|
726 | 797 |
|
727 |
| - return log_level.upper() |
| 798 | + # Powertools log level is set, we use this |
| 799 | + return powertools_log_level.upper() |
728 | 800 |
|
729 | 801 |
|
730 | 802 | def set_package_logger(
|
|
0 commit comments