Skip to content

Commit 9b62086

Browse files
author
Artem Krivonos
committed
Log top level errors as JSON
1 parent b5986dd commit 9b62086

File tree

3 files changed

+50
-14
lines changed

3 files changed

+50
-14
lines changed

awslambdaric/bootstrap.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@
1313
from .lambda_context import LambdaContext
1414
from .lambda_runtime_client import LambdaRuntimeClient
1515
from .lambda_runtime_exception import FaultException
16-
from .lambda_runtime_log_utils import INVOCATION_LOGGING_CONTEXT, JsonFormatter
16+
from .lambda_runtime_log_utils import (
17+
INVOCATION_LOGGING_CONTEXT,
18+
JsonFormatter,
19+
DATETIME_FORMAT,
20+
)
1721
from .lambda_runtime_marshaller import to_json
1822

1923
ERROR_LOG_LINE_TERMINATE = "\r"
2024
ERROR_LOG_IDENT = "\u00a0" # NO-BREAK SPACE U+00A0
25+
RUNTIME_ERROR_LOGGER_NAME = "system"
2126

2227

2328
def _get_handler(handler):
@@ -74,8 +79,15 @@ def result(*args):
7479
return result
7580

7681

77-
def make_error(error_message, error_type, stack_trace, invoke_id=None):
82+
def make_error(
83+
error_message, error_type, stack_trace, invoke_id=None, level=logging.ERROR
84+
):
7885
result = {
86+
"timestamp": time.strftime(
87+
DATETIME_FORMAT, logging.Formatter.converter(time.time())
88+
),
89+
"level": level,
90+
"logger": RUNTIME_ERROR_LOGGER_NAME,
7991
"errorMessage": error_message if error_message else "",
8092
"errorType": error_type if error_type else "",
8193
"requestId": invoke_id if invoke_id is not None else "",
@@ -94,6 +106,19 @@ def replace_line_indentation(line, indent_char, new_indent_char):
94106

95107

96108
def log_error(error_result, log_sink):
109+
if AWS_LAMBDA_LOG_FORMAT == "JSON":
110+
log_error_json(error_result, log_sink)
111+
else:
112+
log_error_text(error_result, log_sink)
113+
114+
115+
def log_error_json(error_result, log_sink):
116+
log_sink.log_error(
117+
[to_json(error_result)], log_level=error_result.get("level", logging.ERROR)
118+
)
119+
120+
121+
def log_error_text(error_result, log_sink):
97122
error_description = "[ERROR]"
98123

99124
error_result_type = error_result.get("errorType")
@@ -120,7 +145,9 @@ def log_error(error_result, log_sink):
120145
replace_line_indentation(trace_line, " ", ERROR_LOG_IDENT)
121146
]
122147

123-
log_sink.log_error(error_message_lines)
148+
log_sink.log_error(
149+
error_message_lines, log_level=error_result.get("level", logging.ERROR)
150+
)
124151

125152

126153
def handle_event_request(
@@ -153,7 +180,9 @@ def handle_event_request(
153180
)
154181
except FaultException as e:
155182
xray_fault = make_xray_fault("LambdaValidationError", e.msg, os.getcwd(), [])
156-
error_result = make_error(e.msg, e.exception_type, e.trace, invoke_id)
183+
error_result = make_error(
184+
e.msg, e.exception_type, e.trace, invoke_id, level=logging.FATAL
185+
)
157186

158187
except Exception:
159188
etype, value, tb = sys.exc_info()
@@ -222,7 +251,10 @@ def build_fault_result(exc_info, msg):
222251
break
223252

224253
return make_error(
225-
msg if msg else str(value), etype.__name__, traceback.format_list(tb_tuples)
254+
msg if msg else str(value),
255+
etype.__name__,
256+
traceback.format_list(tb_tuples),
257+
level=logging.FATAL,
226258
)
227259

228260

@@ -304,10 +336,10 @@ def __enter__(self):
304336
def __exit__(self, exc_type, exc_value, exc_tb):
305337
pass
306338

307-
def log(self, msg, log_level=None, log_format=None):
339+
def log(self, msg, log_level=logging.NOTSET, log_format="TEXT"):
308340
sys.stdout.write(msg)
309341

310-
def log_error(self, message_lines):
342+
def log_error(self, message_lines, log_level=logging.ERROR):
311343
error_message = ERROR_LOG_LINE_TERMINATE.join(message_lines) + "\n"
312344
sys.stdout.write(error_message)
313345

@@ -377,9 +409,9 @@ def log(self, msg, log_level=logging.NOTSET, log_format="TEXT"):
377409
)
378410
self.file.write(log_msg)
379411

380-
def log_error(self, message_lines):
412+
def log_error(self, message_lines, log_level=logging.ERROR):
381413
error_message = "\n".join(message_lines)
382-
self.log(error_message, log_level=logging.FATAL)
414+
self.log(error_message, log_level=log_level)
383415

384416

385417
def update_xray_env_variable(xray_trace_id):

awslambdaric/lambda_runtime_log_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from .lambda_runtime_marshaller import to_json
99

10+
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
1011
RESERVED_FIELDS = {
1112
"name",
1213
"msg",
@@ -35,7 +36,7 @@
3536

3637
class JsonFormatter(logging.Formatter):
3738
def __init__(self):
38-
super().__init__(datefmt="%Y-%m-%dT%H:%M:%SZ", validate=False)
39+
super().__init__(datefmt=DATETIME_FORMAT, validate=False)
3940

4041
@staticmethod
4142
def format_stacktrace(exc_info):

tests/test_bootstrap.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@ def raise_exception_handler(json_input, lambda_context):
398398
)
399399

400400
expected_response = {
401+
"level": 50,
402+
"logger": "system",
401403
"errorType": "FaultExceptionType",
402404
"errorMessage": "Fault exception msg",
403405
"requestId": "invoke_id",
@@ -417,6 +419,7 @@ def raise_exception_handler(json_input, lambda_context):
417419
)
418420
args, _ = self.lambda_runtime.post_invocation_error.call_args
419421
error_response = json.loads(args[1])
422+
self.assertIsNotNone(error_response.pop("timestamp"))
420423
self.assertEqual(args[0], "invoke_id")
421424
self.assertEqual(error_response.items(), expected_response.items())
422425
self.assertEqual(
@@ -938,7 +941,7 @@ def test_log_error_framed_log_sink(self):
938941
content = f.read()
939942

940943
frame_type = int.from_bytes(content[:4], "big")
941-
self.assertEqual(frame_type, 0xA55A001B)
944+
self.assertEqual(frame_type, 0xA55A0017)
942945

943946
length = int.from_bytes(content[4:8], "big")
944947
self.assertEqual(length, len(expected_logged_error.encode("utf8")))
@@ -984,7 +987,7 @@ def test_log_error_indentation_framed_log_sink(self):
984987
content = f.read()
985988

986989
frame_type = int.from_bytes(content[:4], "big")
987-
self.assertEqual(frame_type, 0xA55A001B)
990+
self.assertEqual(frame_type, 0xA55A0017)
988991

989992
length = int.from_bytes(content[4:8], "big")
990993
self.assertEqual(length, len(expected_logged_error.encode("utf8")))
@@ -1027,7 +1030,7 @@ def test_log_error_empty_stacktrace_line_framed_log_sink(self):
10271030
content = f.read()
10281031

10291032
frame_type = int.from_bytes(content[:4], "big")
1030-
self.assertEqual(frame_type, 0xA55A001B)
1033+
self.assertEqual(frame_type, 0xA55A0017)
10311034

10321035
length = int.from_bytes(content[4:8], "big")
10331036
self.assertEqual(length, len(expected_logged_error))
@@ -1064,7 +1067,7 @@ def test_log_error_invokeId_line_framed_log_sink(self):
10641067
content = f.read()
10651068

10661069
frame_type = int.from_bytes(content[:4], "big")
1067-
self.assertEqual(frame_type, 0xA55A001B)
1070+
self.assertEqual(frame_type, 0xA55A0017)
10681071

10691072
length = int.from_bytes(content[4:8], "big")
10701073
self.assertEqual(length, len(expected_logged_error))

0 commit comments

Comments
 (0)