Skip to content

Commit 9d69f32

Browse files
author
Artem Krivonos
committed
Add JsonFormatter, INSTANCE_LOGGING_CONTEXT, INVOCATION_LOGGING_CONTEXT and environ variable AWS_LAMBDA_LOG_FORMAT that manages logging format
1 parent 97dee25 commit 9d69f32

File tree

2 files changed

+109
-5
lines changed

2 files changed

+109
-5
lines changed

awslambdaric/bootstrap.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .lambda_runtime_client import LambdaRuntimeClient
1515
from .lambda_runtime_exception import FaultException
1616
from .lambda_runtime_marshaller import to_json
17+
from .lambda_runtime_log_utils import JsonFormatter, INSTANCE_LOGGING_CONTEXT
1718

1819
ERROR_LOG_LINE_TERMINATE = "\r"
1920
ERROR_LOG_IDENT = "\u00a0" # NO-BREAK SPACE U+00A0
@@ -367,6 +368,7 @@ def create_log_sink():
367368
return StandardLogSink()
368369

369370

371+
AWS_LAMBDA_LOG_FORMAT = os.environ.get("AWS_LAMBDA_LOG_FORMAT", "TEXT")
370372
_GLOBAL_AWS_REQUEST_ID = None
371373

372374

@@ -381,12 +383,17 @@ def run(app_root, handler, lambda_runtime_api_addr):
381383
logging.Formatter.converter = time.gmtime
382384
logger = logging.getLogger()
383385
logger_handler = LambdaLoggerHandler(log_sink)
384-
logger_handler.setFormatter(
385-
logging.Formatter(
386-
"[%(levelname)s]\t%(asctime)s.%(msecs)03dZ\t%(aws_request_id)s\t%(message)s\n",
387-
"%Y-%m-%dT%H:%M:%S",
386+
if AWS_LAMBDA_LOG_FORMAT == "JSON":
387+
logger_handler.setFormatter(JsonFormatter())
388+
logging._levelToName[logging.CRITICAL] = "FATAL"
389+
else:
390+
logger_handler.setFormatter(
391+
logging.Formatter(
392+
"[%(levelname)s]\t%(asctime)s.%(msecs)03dZ\t%(aws_request_id)s\t%(message)s\n",
393+
"%Y-%m-%dT%H:%M:%S",
394+
)
388395
)
389-
)
396+
390397
logger_handler.addFilter(LambdaLoggerFilter())
391398
logger.addHandler(logger_handler)
392399

@@ -403,6 +410,7 @@ def run(app_root, handler, lambda_runtime_api_addr):
403410

404411
while True:
405412
event_request = lambda_runtime_client.wait_next_invocation()
413+
INSTANCE_LOGGING_CONTEXT.clear()
406414

407415
_GLOBAL_AWS_REQUEST_ID = event_request.invoke_id
408416

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
"""
4+
5+
import logging
6+
7+
from .lambda_runtime_marshaller import to_json
8+
9+
10+
RESERVED_FIELDS = {
11+
"name",
12+
"msg",
13+
"args",
14+
"levelname",
15+
"levelno",
16+
"pathname",
17+
"filename",
18+
"module",
19+
"exc_info",
20+
"exc_text",
21+
"stack_info",
22+
"lineno",
23+
"funcName",
24+
"created",
25+
"msecs",
26+
"relativeCreated",
27+
"thread",
28+
"threadName",
29+
"processName",
30+
"process",
31+
"aws_request_id",
32+
}
33+
34+
35+
class JsonFormatter(logging.Formatter):
36+
def __init__(self):
37+
super().__init__(datefmt="%Y-%m-%dT%H:%M:%SZ", validate=False)
38+
39+
def format_stacktrace(self, exc_info):
40+
if not exc_info:
41+
return None
42+
return self.formatException(exc_info)
43+
44+
@staticmethod
45+
def format_exception_name(exc_info):
46+
if not exc_info:
47+
return None
48+
49+
return exc_info[0].__name__
50+
51+
@staticmethod
52+
def format_exception(exc_info):
53+
if not exc_info:
54+
return None
55+
56+
return str(exc_info[1])
57+
58+
@staticmethod
59+
def format_location(record: logging.LogRecord):
60+
return f"{record.pathname}:{record.funcName}:{record.lineno}"
61+
62+
def format(self, record: logging.LogRecord) -> str:
63+
result = {
64+
"timestamp": self.formatTime(record, self.datefmt),
65+
"level": record.levelname,
66+
"message": record.getMessage(),
67+
"logger": record.name,
68+
"stackTrace": self.format_stacktrace(record.exc_info),
69+
"errorType": self.format_exception_name(record.exc_info),
70+
"errorMessage": self.format_exception(record.exc_info),
71+
"requestId": getattr(record, "aws_request_id", None),
72+
"location": self.format_location(record),
73+
}
74+
result.update(
75+
(key, value)
76+
for key, value in record.__dict__.items()
77+
if key not in RESERVED_FIELDS and key not in result
78+
)
79+
result.update(INSTANCE_LOGGING_CONTEXT)
80+
result.update(INVOCATION_LOGGING_CONTEXT)
81+
82+
result = {k: v for k, v in result.items() if v is not None}
83+
84+
return to_json(result)
85+
86+
87+
INSTANCE_LOGGING_CONTEXT = dict()
88+
INVOCATION_LOGGING_CONTEXT = dict()
89+
90+
91+
def lambda_instance_logs_update(key: str, value: str):
92+
INSTANCE_LOGGING_CONTEXT[str(key)] = str(value)
93+
94+
95+
def lambda_invocation_logs_update(key: str, value: str):
96+
INVOCATION_LOGGING_CONTEXT[str(key)] = str(value)

0 commit comments

Comments
 (0)