Skip to content

Commit b498b0b

Browse files
author
Michael Brewer
committed
feat(logging): inject correlation_id
Prototype idea for correlation_id support in logger
1 parent a697436 commit b498b0b

File tree

2 files changed

+57
-3
lines changed

2 files changed

+57
-3
lines changed

aws_lambda_powertools/logging/logger.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import os
55
import random
66
import sys
7-
from typing import Any, Callable, Dict, Union
7+
from typing import Any, Callable, Dict, Optional, Union
8+
9+
import jmespath
810

911
from ..shared import constants
1012
from ..shared.functions import resolve_env_var_choice, resolve_truthy_env_var_choice
@@ -36,6 +38,27 @@ def _is_cold_start() -> bool:
3638
return cold_start
3739

3840

41+
def _get_correlation_id_path(path: Union[str, int]) -> Optional[str]:
42+
"""
43+
44+
Parameters
45+
----------
46+
path :
47+
JMESPath
48+
Returns
49+
-------
50+
str, optional
51+
Returns the JMESPath
52+
"""
53+
if path is None:
54+
return None
55+
56+
# NOTE: We could have a enum of standard event types
57+
# like "requestContext.requestId" or API Gateway Proxy Events
58+
59+
return path
60+
61+
3962
# PyCharm does not support autocomplete via getattr
4063
# so we need to return to subclassing removed in #97
4164
# All methods/properties continue to be proxied to inner logger
@@ -204,7 +227,12 @@ def _configure_sampling(self):
204227
f"Please review POWERTOOLS_LOGGER_SAMPLE_RATE environment variable."
205228
)
206229

207-
def inject_lambda_context(self, lambda_handler: Callable[[Dict, Any], Any] = None, log_event: bool = None):
230+
def inject_lambda_context(
231+
self,
232+
lambda_handler: Callable[[Dict, Any], Any] = None,
233+
log_event: bool = None,
234+
correlation_id_path: Union[str, int] = None,
235+
):
208236
"""Decorator to capture Lambda contextual info and inject into logger
209237
210238
Parameters
@@ -213,6 +241,8 @@ def inject_lambda_context(self, lambda_handler: Callable[[Dict, Any], Any] = Non
213241
Method to inject the lambda context
214242
log_event : bool, optional
215243
Instructs logger to log Lambda Event, by default False
244+
correlation_id_path: str, optional
245+
Can be a JMESPath or one of the standard ones
216246
217247
Environment variables
218248
---------------------
@@ -251,18 +281,24 @@ def handler(event, context):
251281
# Return a partial function with args filled
252282
if lambda_handler is None:
253283
logger.debug("Decorator called with parameters")
254-
return functools.partial(self.inject_lambda_context, log_event=log_event)
284+
return functools.partial(
285+
self.inject_lambda_context, log_event=log_event, correlation_id_path=correlation_id_path
286+
)
255287

256288
log_event = resolve_truthy_env_var_choice(
257289
choice=log_event, env=os.getenv(constants.LOGGER_LOG_EVENT_ENV, "false")
258290
)
291+
correlation_id_path = _get_correlation_id_path(correlation_id_path)
259292

260293
@functools.wraps(lambda_handler)
261294
def decorate(event, context):
262295
lambda_context = build_lambda_context_model(context)
263296
cold_start = _is_cold_start()
264297
self.structure_logs(append=True, cold_start=cold_start, **lambda_context.__dict__)
265298

299+
if correlation_id_path:
300+
self.structure_logs(append=True, correlation_id=jmespath.search(correlation_id_path, event))
301+
266302
if log_event:
267303
logger.debug("Event received")
268304
self.info(event)

tests/functional/test_logger.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,21 @@ def test_logger_exception_extract_exception_name(stdout, service_name):
437437
# THEN we expect a "exception_name" to be "ValueError"
438438
log = capture_logging_output(stdout)
439439
assert "ValueError" == log["exception_name"]
440+
441+
442+
def test_logger_correlation_id(lambda_context, stdout, service_name):
443+
# GIVEN
444+
logger = Logger(service=service_name, stream=stdout)
445+
request_id = "xxx"
446+
mock_event = {"requestContext": {"requestId": request_id}}
447+
448+
@logger.inject_lambda_context(correlation_id_path="requestContext.requestId")
449+
def handler(_1, _2):
450+
logger.info("Foo")
451+
452+
# WHEN
453+
handler(mock_event, lambda_context)
454+
455+
# THEN
456+
log = capture_logging_output(stdout)
457+
assert request_id == log["correlation_id"]

0 commit comments

Comments
 (0)