diff --git a/aws_lambda_powertools/logging/formatter.py b/aws_lambda_powertools/logging/formatter.py index 8b34b326435..ac623303ab1 100644 --- a/aws_lambda_powertools/logging/formatter.py +++ b/aws_lambda_powertools/logging/formatter.py @@ -48,6 +48,9 @@ class BasePowertoolsFormatter(logging.Formatter, metaclass=ABCMeta): def append_keys(self, **additional_keys) -> None: raise NotImplementedError() + def get_current_keys(self) -> Dict[str, Any]: + return {} + def remove_keys(self, keys: Iterable[str]) -> None: raise NotImplementedError() @@ -231,6 +234,9 @@ def formatTime(self, record: logging.LogRecord, datefmt: Optional[str] = None) - def append_keys(self, **additional_keys) -> None: self.log_format.update(additional_keys) + def get_current_keys(self) -> Dict[str, Any]: + return self.log_format + def remove_keys(self, keys: Iterable[str]) -> None: for key in keys: self.log_format.pop(key, None) diff --git a/aws_lambda_powertools/logging/logger.py b/aws_lambda_powertools/logging/logger.py index f86833a7851..dc03e1af8eb 100644 --- a/aws_lambda_powertools/logging/logger.py +++ b/aws_lambda_powertools/logging/logger.py @@ -583,6 +583,9 @@ def debug( def append_keys(self, **additional_keys: object) -> None: self.registered_formatter.append_keys(**additional_keys) + def get_current_keys(self) -> Dict[str, Any]: + return self.registered_formatter.get_current_keys() + def remove_keys(self, keys: Iterable[str]) -> None: self.registered_formatter.remove_keys(keys) diff --git a/docs/core/logger.md b/docs/core/logger.md index 8c915fcd589..bf600e285ca 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -274,6 +274,16 @@ Logger is commonly initialized in the global scope. Due to [Lambda Execution Con --8<-- "examples/logger/src/clear_state_event_two.json" ``` +### Accessing currently configured keys + +You can view all currently configured keys from the Logger state using the `get_current_keys()` method. This method is useful when you need to avoid overwriting keys that are already configured. + +=== "get_current_keys.py" + + ```python hl_lines="4 11" + --8<-- "examples/logger/src/get_current_keys.py" + ``` + ### Log levels The default log level is `INFO`. It can be set using the `level` constructor option, `setLevel()` method or by using the `POWERTOOLS_LOG_LEVEL` environment variable. @@ -732,7 +742,7 @@ The `log` argument is the final log record containing [our standard keys](#stand For exceptional cases where you want to completely replace our formatter logic, you can subclass `BasePowertoolsFormatter`. ???+ warning - You will need to implement `append_keys`, `clear_state`, override `format`, and optionally `remove_keys` to keep the same feature set Powertools for AWS Lambda (Python) Logger provides. This also means keeping state of logging keys added. + You will need to implement `append_keys`, `clear_state`, override `format`, and optionally `get_current_keys`, and `remove_keys` to keep the same feature set Powertools for AWS Lambda (Python) Logger provides. This also means tracking the added logging keys. === "bring_your_own_formatter_from_scratch.py" diff --git a/examples/logger/src/bring_your_own_formatter_from_scratch.py b/examples/logger/src/bring_your_own_formatter_from_scratch.py index b7e7761b562..425f00b0d28 100644 --- a/examples/logger/src/bring_your_own_formatter_from_scratch.py +++ b/examples/logger/src/bring_your_own_formatter_from_scratch.py @@ -1,6 +1,6 @@ import json import logging -from typing import Iterable, List, Optional +from typing import Any, Dict, Iterable, List, Optional from aws_lambda_powertools import Logger from aws_lambda_powertools.logging.formatter import BasePowertoolsFormatter @@ -16,6 +16,9 @@ def append_keys(self, **additional_keys): # also used by `inject_lambda_context` decorator self.log_format.update(additional_keys) + def current_keys(self) -> Dict[str, Any]: + return self.log_format + def remove_keys(self, keys: Iterable[str]): for key in keys: self.log_format.pop(key, None) diff --git a/examples/logger/src/get_current_keys.py b/examples/logger/src/get_current_keys.py new file mode 100644 index 00000000000..c0ae49165b7 --- /dev/null +++ b/examples/logger/src/get_current_keys.py @@ -0,0 +1,14 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() + + +@logger.inject_lambda_context +def lambda_handler(event: dict, context: LambdaContext) -> str: + logger.info("Collecting payment") + + if "order" not in logger.get_current_keys(): + logger.append_keys(order=event.get("order")) + + return "hello world" diff --git a/tests/functional/test_logger.py b/tests/functional/test_logger.py index d9e3b8d4e37..7aa4037cb9c 100644 --- a/tests/functional/test_logger.py +++ b/tests/functional/test_logger.py @@ -9,10 +9,9 @@ import string import sys import warnings -from ast import Dict from collections import namedtuple from datetime import datetime, timezone -from typing import Any, Callable, Iterable, List, Optional, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, Union import pytest @@ -606,6 +605,45 @@ def test_logger_append_remove_keys(stdout, service_name): assert (extra_keys.items() <= keys_removed_log.items()) is False +def test_logger_append_and_show_current_keys(stdout, service_name): + # GIVEN a Logger is initialized + logger = Logger(service=service_name, stream=stdout) + extra_keys = {"request_id": "id", "context": "value"} + + # WHEN keys are updated + logger.append_keys(**extra_keys) + + # THEN appended keys must be present in logger + current_keys = logger.get_current_keys() + assert "request_id" in current_keys + assert "context" in current_keys + + +def test_logger_formatter_without_get_current_keys_method(stdout, service_name): + class CustomFormatter(BasePowertoolsFormatter): + def append_keys(self, **additional_keys): + # Fake method + pass + + def clear_state(self) -> None: + # Fake method + pass + + custom_formater = CustomFormatter() + + # GIVEN a Logger is initialized with a Logger Formatter from scratch + + logger = Logger(service=service_name, stream=stdout, logger_formatter=custom_formater) + extra_keys = {"request_id": "id", "context": "value"} + + # WHEN keys are updated + logger.append_keys(**extra_keys) + + # THEN the appended keys will not persist + # unless the customer implements the required methods and persists the log_format + assert logger.get_current_keys() == {} + + def test_logger_custom_formatter(stdout, service_name, lambda_context): class CustomFormatter(BasePowertoolsFormatter): custom_format = {}