Skip to content

Commit 573d6f9

Browse files
committed
fix(logger): ensure state is cleared for all formatters
1 parent 22f8b9a commit 573d6f9

File tree

3 files changed

+90
-7
lines changed

3 files changed

+90
-7
lines changed

aws_lambda_powertools/logging/formatter.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ def append_keys(self, **additional_keys):
4545
def remove_keys(self, keys: Iterable[str]):
4646
raise NotImplementedError()
4747

48+
def clear_state(self):
49+
"""Removes any previously added logging keys"""
50+
raise NotImplementedError()
51+
4852

4953
class LambdaPowertoolsFormatter(BasePowertoolsFormatter):
5054
"""AWS Lambda Powertools Logging formatter.
@@ -180,6 +184,9 @@ def remove_keys(self, keys: Iterable[str]):
180184
for key in keys:
181185
self.log_format.pop(key, None)
182186

187+
def clear_state(self):
188+
self.log_format = dict.fromkeys(self.log_record_order)
189+
183190
@staticmethod
184191
def _build_default_keys():
185192
return {

aws_lambda_powertools/logging/logger.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -385,14 +385,29 @@ def structure_logs(self, append: bool = False, **keys):
385385
append : bool, optional
386386
append keys provided to logger formatter, by default False
387387
"""
388+
# There are 3 operational modes for this method
389+
## 1. Append new keys to the current logger formatter; deprecated in favour of append_keys
390+
## 2. Register a Powertools Formatter for the first time
391+
## 3. Add new keys and discard existing to the registered formatter
388392

389393
if append:
390-
# Maintenance: Add deprecation warning for major version. Refer to append_keys() when docs are updated
391-
self.append_keys(**keys)
392-
else:
393-
log_keys = {**self._default_log_keys, **keys}
394+
# Maintenance: Add deprecation warning for major version
395+
return self.append_keys(**keys)
396+
397+
log_keys = {**self._default_log_keys, **keys}
398+
399+
# Behaviour 2
400+
is_logger_preconfigured = getattr(self._logger, "init", False)
401+
if not is_logger_preconfigured:
394402
formatter = self.logger_formatter or LambdaPowertoolsFormatter(**log_keys) # type: ignore
395-
self.registered_handler.setFormatter(formatter)
403+
return self.registered_handler.setFormatter(formatter)
404+
405+
# Behaviour 3
406+
try:
407+
self.registered_formatter.clear_state()
408+
self.registered_formatter.append_keys(**log_keys)
409+
except (AttributeError, NotImplementedError):
410+
logger.warning(f"Formatter {self.registered_formatter} doesn't implement clear_state method; ignoring...")
396411

397412
def set_correlation_id(self, value: Optional[str]):
398413
"""Sets the correlation_id in the logging json
@@ -438,6 +453,9 @@ def _get_caller_filename():
438453
caller_frame = frame.f_back.f_back.f_back
439454
return caller_frame.f_globals["__name__"]
440455

456+
def _reset_logger_state(self):
457+
self.registered_formatter.clear_state()
458+
441459

442460
def set_package_logger(
443461
level: Union[str, int] = logging.DEBUG,

tests/functional/test_logger.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55
import random
66
import re
77
import string
8+
from ast import Dict
89
from collections import namedtuple
910
from datetime import datetime, timezone
10-
from typing import Iterable
11+
from typing import Any, Callable, Iterable, List, Optional, Union
1112

1213
import pytest
1314

1415
from aws_lambda_powertools import Logger, Tracer
1516
from aws_lambda_powertools.logging import correlation_paths
1617
from aws_lambda_powertools.logging.exceptions import InvalidLoggerSamplingRateError
17-
from aws_lambda_powertools.logging.formatter import BasePowertoolsFormatter
18+
from aws_lambda_powertools.logging.formatter import BasePowertoolsFormatter, LambdaPowertoolsFormatter
1819
from aws_lambda_powertools.logging.logger import set_package_logger
1920
from aws_lambda_powertools.shared import constants
2021
from aws_lambda_powertools.utilities.data_classes import S3Event, event_source
@@ -564,6 +565,63 @@ def handler(event, context):
564565
assert logger.get_correlation_id() is None
565566

566567

568+
def test_logger_custom_powertools_formatter_clear_state(stdout, service_name, lambda_context):
569+
class CustomFormatter(LambdaPowertoolsFormatter):
570+
def __init__(
571+
self,
572+
json_serializer: Optional[Callable[[Dict], str]] = None,
573+
json_deserializer: Optional[Callable[[Union[Dict, str, bool, int, float]], str]] = None,
574+
json_default: Optional[Callable[[Any], Any]] = None,
575+
datefmt: Optional[str] = None,
576+
use_datetime_directive: bool = False,
577+
log_record_order: Optional[List[str]] = None,
578+
utc: bool = False,
579+
**kwargs,
580+
):
581+
super().__init__(
582+
json_serializer,
583+
json_deserializer,
584+
json_default,
585+
datefmt,
586+
use_datetime_directive,
587+
log_record_order,
588+
utc,
589+
**kwargs,
590+
)
591+
592+
custom_formatter = CustomFormatter()
593+
594+
# GIVEN a Logger is initialized with a custom formatter
595+
logger = Logger(service=service_name, stream=stdout, logger_formatter=custom_formatter)
596+
597+
# WHEN a lambda function is decorated with logger
598+
# and state is to be cleared in the next invocation
599+
@logger.inject_lambda_context(clear_state=True)
600+
def handler(event, context):
601+
if event.get("add_key"):
602+
logger.append_keys(my_key="value")
603+
logger.info("Hello")
604+
605+
handler({"add_key": True}, lambda_context)
606+
handler({}, lambda_context)
607+
608+
lambda_context_keys = (
609+
"function_name",
610+
"function_memory_size",
611+
"function_arn",
612+
"function_request_id",
613+
)
614+
615+
first_log, second_log = capture_multiple_logging_statements_output(stdout)
616+
617+
# THEN my_key should only present once
618+
# and lambda contextual info should also be in both logs
619+
assert "my_key" in first_log
620+
assert "my_key" not in second_log
621+
assert all(k in first_log for k in lambda_context_keys)
622+
assert all(k in second_log for k in lambda_context_keys)
623+
624+
567625
def test_logger_custom_handler(lambda_context, service_name, tmp_path):
568626
# GIVEN a Logger is initialized with a FileHandler
569627
log_file = tmp_path / "log.json"

0 commit comments

Comments
 (0)