Skip to content

Commit 9b33d29

Browse files
feat(logger): add clear_state method (#5956)
* feat(logger): add clear_state method to Logger * changes after feedback * change reset default keys --------- Co-authored-by: Leandro Damascena <[email protected]>
1 parent 6971eb2 commit 9b33d29

File tree

6 files changed

+129
-2
lines changed

6 files changed

+129
-2
lines changed

Diff for: aws_lambda_powertools/logging/logger.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def __init__(
222222
choice=sampling_rate,
223223
env=os.getenv(constants.LOGGER_LOG_SAMPLING_RATE),
224224
)
225+
self._default_log_keys: dict[str, Any] = {"service": self.service, "sampling_rate": self.sampling_rate}
225226
self.child = child
226227
self.logger_formatter = logger_formatter
227228
self._stream = stream or sys.stdout
@@ -231,7 +232,6 @@ def __init__(
231232
self._is_deduplication_disabled = resolve_truthy_env_var_choice(
232233
env=os.getenv(constants.LOGGER_LOG_DEDUPLICATION_ENV, "false"),
233234
)
234-
self._default_log_keys = {"service": self.service, "sampling_rate": self.sampling_rate}
235235
self._logger = self._get_logger()
236236

237237
# NOTE: This is primarily to improve UX, so IDEs can autocomplete LambdaPowertoolsFormatter options
@@ -605,6 +605,14 @@ def append_context_keys(self, **additional_keys: Any) -> Generator[None, None, N
605605
with self.registered_formatter.append_context_keys(**additional_keys):
606606
yield
607607

608+
def clear_state(self) -> None:
609+
"""Removes all custom keys that were appended to the Logger."""
610+
# Clear all custom keys from the formatter
611+
self.registered_formatter.clear_state()
612+
613+
# Reset to default keys
614+
self.structure_logs(**self._default_log_keys)
615+
608616
# These specific thread-safe methods are necessary to manage shared context in concurrent environments.
609617
# They prevent race conditions and ensure data consistency across multiple threads.
610618
def thread_safe_append_keys(self, **additional_keys: object) -> None:

Diff for: docs/core/logger.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -274,13 +274,15 @@ You can remove any additional key from Logger state using `remove_keys`.
274274

275275
#### Clearing all state
276276

277+
##### Decorator with clear_state
278+
277279
Logger is commonly initialized in the global scope. Due to [Lambda Execution Context reuse](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html){target="_blank"}, this means that custom keys can be persisted across invocations. If you want all custom keys to be deleted, you can use `clear_state=True` param in `inject_lambda_context` decorator.
278280

279281
???+ tip "Tip: When is this useful?"
280282
It is useful when you add multiple custom keys conditionally, instead of setting a default `None` value if not present. Any key with `None` value is automatically removed by Logger.
281283

282284
???+ danger "Danger: This can have unintended side effects if you use Layers"
283-
Lambda Layers code is imported before the Lambda handler.
285+
Lambda Layers code is imported before the Lambda handler. When a Lambda function starts, it first imports and executes all code in the Layers (including any global scope code) before proceeding to the function's own code.
284286

285287
This means that `clear_state=True` will instruct Logger to remove any keys previously added before Lambda handler execution proceeds.
286288

@@ -304,6 +306,27 @@ Logger is commonly initialized in the global scope. Due to [Lambda Execution Con
304306
--8<-- "examples/logger/src/clear_state_event_two.json"
305307
```
306308

309+
##### clear_state method
310+
311+
You can call `clear_state()` as a method explicitly within your code to clear appended keys at any point during the execution of your Lambda invocation.
312+
313+
=== "clear_state_method.py"
314+
315+
```python hl_lines="12"
316+
--8<-- "examples/logger/src/clear_state_method.py"
317+
```
318+
=== "Output before clear_state()"
319+
320+
```json hl_lines="9 17"
321+
--8<-- "examples/logger/src/before_clear_state.json"
322+
```
323+
324+
=== "Output after clear_state()"
325+
326+
```json hl_lines="4"
327+
--8<-- "examples/logger/src/after_clear_state.json"
328+
```
329+
307330
### Accessing currently configured keys
308331

309332
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.

Diff for: examples/logger/src/after_clear_state.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"level": "INFO",
3+
"location": "lambda_handler:126",
4+
"message": "State after clearing - only show default keys",
5+
"timestamp": "2025-01-30 13:56:03,158-0300",
6+
"service": "payment"
7+
}

Diff for: examples/logger/src/before_clear_state.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"logs": [
3+
{
4+
"level": "INFO",
5+
"location": "lambda_handler:122",
6+
"message": "Starting order processing",
7+
"timestamp": "2025-01-30 13:56:03,157-0300",
8+
"service": "payment",
9+
"order_id": "12345"
10+
},
11+
{
12+
"level": "INFO",
13+
"location": "lambda_handler:124",
14+
"message": "Final state before clearing",
15+
"timestamp": "2025-01-30 13:56:03,157-0300",
16+
"service": "payment",
17+
"order_id": "12345"
18+
}
19+
]
20+
}

Diff for: examples/logger/src/clear_state_method.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from aws_lambda_powertools import Logger
2+
from aws_lambda_powertools.utilities.typing import LambdaContext
3+
4+
logger = Logger(service="payment", level="DEBUG")
5+
6+
7+
def lambda_handler(event: dict, context: LambdaContext) -> str:
8+
try:
9+
logger.append_keys(order_id="12345")
10+
logger.info("Starting order processing")
11+
finally:
12+
logger.info("Final state before clearing")
13+
logger.clear_state()
14+
logger.info("State after clearing - only show default keys")
15+
return "Completed"

Diff for: tests/functional/logger/required_dependencies/test_logger.py

+54
Original file line numberDiff line numberDiff line change
@@ -1232,3 +1232,57 @@ def test_logger_change_level_child_logger(stdout, service_name):
12321232
logs = list(stdout.getvalue().strip().split("\n"))
12331233
assert len(logs) == 1
12341234
assert "service" in logs[0]
1235+
1236+
1237+
def test_clear_state_with_append_keys():
1238+
# GIVEN a Logger is initialized
1239+
logger = Logger(service="service_name", stream=stdout)
1240+
1241+
# WHEN append keys are added
1242+
logger.append_keys(custom_key="custom_key")
1243+
logger.info("message with appended keys")
1244+
logger.clear_state()
1245+
1246+
# THEN context keys should be cleared
1247+
assert "custom_key" not in logger.get_current_keys()
1248+
1249+
1250+
def test_clear_state(stdout, service_name):
1251+
# GIVEN a Logger is initialized
1252+
logger = Logger(service=service_name, stream=stdout)
1253+
logger.info("message for the user")
1254+
1255+
# WHEN the clear_state method is called
1256+
logger.clear_state()
1257+
1258+
# THEN the logger's current keys should be reset to their default values
1259+
expected_keys = {
1260+
"level": "%(levelname)s",
1261+
"location": "%(funcName)s:%(lineno)d",
1262+
"message": None,
1263+
"timestamp": "%(asctime)s",
1264+
"service": service_name,
1265+
"sampling_rate": None,
1266+
}
1267+
assert logger.get_current_keys() == expected_keys
1268+
1269+
1270+
def test_clear_state_log_output(stdout, service_name):
1271+
# GIVEN a Logger is initialized
1272+
logger = Logger(service=service_name, stream=stdout)
1273+
1274+
# WHEN we append a custom key and log
1275+
logger.append_keys(custom_key="test_value")
1276+
logger.info("first message")
1277+
1278+
# AND we clear the state and log again
1279+
logger.clear_state()
1280+
logger.info("second message")
1281+
1282+
# THEN the first log should contain the custom key
1283+
# AND the second log should not contain the custom key
1284+
first_log, second_log = capture_multiple_logging_statements_output(stdout)
1285+
1286+
assert "custom_key" in first_log
1287+
assert first_log["custom_key"] == "test_value"
1288+
assert "custom_key" not in second_log

0 commit comments

Comments
 (0)