Skip to content

Commit 9537890

Browse files
committed
feat: support for extra keys in log messages
1 parent 9473897 commit 9537890

File tree

2 files changed

+52
-3
lines changed

2 files changed

+52
-3
lines changed

aws_lambda_powertools/logging/formatter.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,30 @@
33
import os
44
from typing import Dict, Iterable, Optional, Union
55

6+
STD_LOGGING_KEYS = (
7+
"name",
8+
"msg",
9+
"args",
10+
"levelname",
11+
"levelno",
12+
"pathname",
13+
"filename",
14+
"module",
15+
"exc_info",
16+
"exc_text",
17+
"stack_info",
18+
"lineno",
19+
"funcName",
20+
"created",
21+
"msecs",
22+
"relativeCreated",
23+
"thread",
24+
"threadName",
25+
"processName",
26+
"process",
27+
"asctime",
28+
)
29+
630

731
class JsonFormatter(logging.Formatter):
832
"""AWS Lambda Logging formatter.
@@ -120,15 +144,21 @@ def _extract_log_keys(self, log_record: logging.LogRecord) -> Dict:
120144

121145
formatted_log = {}
122146

123-
# Iterate over new or existing log structure
124-
# replace reserved logging expression e.g. '%(level)s' to 'INFO'
125-
# lastly, add or replace non-reserved keys
147+
# We have to iterate over a default or existing log structure
148+
# then replace any logging expression for reserved keys e.g. '%(level)s' to 'INFO'
149+
# and lastly add or replace incoming keys (those added within the constructor or .structure_logs method)
126150
for key, value in self.log_format.items():
127151
if value and key in self.reserved_keys:
128152
formatted_log[key] = value % record_dict
129153
else:
130154
formatted_log[key] = value
131155

156+
# pick up extra keys when logging a new message e.g. log.info("my message", extra={"additional_key": "value"}
157+
# these messages will be added to the root of the final structure not within `message` key
158+
for key, value in record_dict.items():
159+
if key not in STD_LOGGING_KEYS:
160+
formatted_log[key] = value
161+
132162
return formatted_log
133163

134164
def format(self, record): # noqa: A003

tests/functional/test_logger.py

+19
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,22 @@ def test_logger_do_not_log_twice_when_root_logger_is_setup(stdout, service_name)
381381
# since child's log records propagated to root logger should be rejected
382382
logs = list(stdout.getvalue().strip().split("\n"))
383383
assert len(logs) == 2
384+
385+
386+
def test_logger_extra_kwargs(stdout, service_name):
387+
# GIVEN Logger is initialized
388+
logger = Logger(service=service_name, stream=stdout)
389+
390+
# WHEN `request_id` is an extra field in a log message to the existing structured log
391+
fields = {"request_id": "blah"}
392+
393+
logger.info("with extra fields", extra=fields)
394+
logger.info("without extra fields")
395+
396+
extra_fields_log, no_extra_fields_log = capture_multiple_logging_statements_output(stdout)
397+
398+
# THEN first log should have request_id field in the root structure
399+
assert "request_id" in extra_fields_log
400+
401+
# THEN second log should not have request_id in the root structure
402+
assert "request_id" not in no_extra_fields_log

0 commit comments

Comments
 (0)