-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Emit logs in JSON format #101
Conversation
ping @luben |
def format_stacktrace(self, exc_info): | ||
if not exc_info: | ||
return None | ||
return self.formatException(exc_info) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be:
return traceback.format_tb(exc_info[2])
so we get the trace as an array of frames, not as a string
|
||
|
||
def lambda_instance_logs_update(key: str, value: str): | ||
INSTANCE_LOGGING_CONTEXT[str(key)] = str(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why str(value)
? I think the value should not be stringified - customer may want to attach an object/dict there that will be rendered as such in JSON
awslambdaric/bootstrap.py
Outdated
@@ -367,9 +368,29 @@ def create_log_sink(): | |||
return StandardLogSink() | |||
|
|||
|
|||
AWS_LAMBDA_LOG_FORMAT = os.environ.get("AWS_LAMBDA_LOG_FORMAT", "TEXT") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also take into account the AWS_LAMBDA_LOG_LEVEL
envvar ans set the logger verbosity accordingly
awslambdaric/bootstrap.py
Outdated
@@ -367,9 +368,31 @@ def create_log_sink(): | |||
return StandardLogSink() | |||
|
|||
|
|||
AWS_LAMBDA_LOG_FORMAT = os.environ.get("AWS_LAMBDA_LOG_FORMAT", "TEXT") | |||
AWS_LAMBDA_LOG_LEVEL = os.environ.get("AWS_LAMBDA_LOG_LEVEL", "INFO") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is envvar is not set, we should not default the filtering to info. I the envvar is not set we will not apply the fiter
awslambdaric/bootstrap.py
Outdated
if log_format == "JSON": | ||
logger_handler.setFormatter(JsonFormatter()) | ||
logging._levelToName[logging.CRITICAL] = "FATAL" | ||
logger.setLevel(log_level) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the log level should be set for both Text and JSON is it's defined
Please set flag from Text to JSON in frame header. |
awslambdaric/bootstrap.py
Outdated
|
||
@staticmethod | ||
def set_log_format(frame_type, log_format): | ||
if log_format == "JSON": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we not create new enum here:
class TextFormat(Enum):
JSON=0
TEXT=1
...
def log(self, msg, log_level=logging.NOTSET, log_format: LogFormat:
...
frame_type = frame_type | log_format.value
awslambdaric/bootstrap.py
Outdated
@@ -347,7 +379,7 @@ def log(self, msg): | |||
|
|||
def log_error(self, message_lines): | |||
error_message = "\n".join(message_lines) | |||
self.log(error_message) | |||
self.log(error_message, log_level=logging.FATAL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be ERROR - at least this is what we use today, e.g. [ERROR] ZeroDivisionError: division by zero
LGTM |
awslambdaric/bootstrap.py
Outdated
@@ -333,20 +385,27 @@ def __enter__(self): | |||
def __exit__(self, exc_type, exc_value, exc_tb): | |||
self.file.close() | |||
|
|||
def log(self, msg): | |||
def set_log_level(self, frame_type, log_level): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you inline this function? I think if we expose that name, customer will think that they can change the log level.
awslambdaric/bootstrap.py
Outdated
@@ -333,20 +395,22 @@ def __enter__(self): | |||
def __exit__(self, exc_type, exc_value, exc_tb): | |||
self.file.close() | |||
|
|||
def log(self, msg): | |||
def log(self, msg, log_level=logging.NOTSET, log_format: int = _TEXT_FORMAT): | |||
frame_type = FRAME_TYPES.get((log_format, log_level), DEFAULT_FRAME_TYPE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For existing customers this is going to create new tuple objects and do hashtable lookups. Not too big of a problem, but can we avoid it for the sake of cleaner implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, moved to formatter.
awslambdaric/bootstrap.py
Outdated
+ len(encoded_msg).to_bytes(4, "big") | ||
+ timestamp.to_bytes(8, "big") | ||
+ encoded_msg | ||
) | ||
self.file.write(log_msg) | ||
|
||
def log_error(self, message_lines): | ||
def log_error(self, message_lines, log_level=logging.ERROR): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With feature enabled, is there need in this method? Are jsons multiline too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is used by log_error(error_result, log_sink)
function.
awslambdaric/bootstrap.py
Outdated
@@ -370,6 +434,28 @@ def create_log_sink(): | |||
_GLOBAL_AWS_REQUEST_ID = None | |||
|
|||
|
|||
def setup_logging(log_format, log_level, log_sink): | |||
logging._levelToName[logging.CRITICAL] = "FATAL" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it going to affect existing customers who are structuring their logging themselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not need it anymore.
@@ -13,8 +13,8 @@ | |||
# simplejson's Decimal encoding allows '-NaN' as an output, which is a parse error for json.loads | |||
# to get the good parts of Decimal support, we'll special-case NaN decimals and otherwise duplicate the encoding for decimals the same way simplejson does | |||
class Encoder(json.JSONEncoder): | |||
def __init__(self): | |||
super().__init__(use_decimal=False) | |||
def __init__(self, ensure_ascii=True): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this overload used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
awslambdaric/bootstrap.py
Outdated
) | ||
|
||
if log_level: | ||
logger.setLevel(log_level) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Should we put this part under
if log_format == LogFormat.JSON:
condition? - In case of incorrect value, should we gracefully handle it?
Example:
Code looks like
logging.addLevelName(33, "PEW")
def lambda_handler():
logging.addLevelName(42, "PEWPEW")
Natural desire would be to use env variable to limit log level to PEWPEW
or PEW
, but at the moment of running setup_logging
it's not yet registered.
Adding levels in a handler looks quite strange, but natural at INIT phase. If the intention is to support custom log levels, need to setLevel
at least after INIT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Yeah let's move it
- Let's restrict to standard levels only
awslambdaric/bootstrap.py
Outdated
self.log_sink.log(msg) | ||
|
||
self.log_sink.log( | ||
msg, frame_type=getattr(record, "_frame_type", _FRAME_TYPES.get(())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Should
_FRAME_TYPES.get(())
be replaced with justNone
? - Nit: Is there any value for attribute lookup for non-JSON logging? If no, would be cleaner to avoid doing it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Yeah, it's a leftover
- There is no value in attribute lookup here. But I'm not sure how to avoid it.
awslambdaric/bootstrap.py
Outdated
@@ -370,6 +416,27 @@ def create_log_sink(): | |||
_GLOBAL_AWS_REQUEST_ID = None | |||
|
|||
|
|||
def setup_logging(log_format, log_level, log_sink): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's hide this behind an _
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please
- Squash your feature commits to make history cleaner
- Improve PR description
super().__init__(datefmt=_DATETIME_FORMAT) | ||
|
||
@staticmethod | ||
def format_stacktrace(exc_info): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method and below - are they supposed to be private? If yes, let's have them mangled with __
66ef14d
to
4aae5fe
Compare
0fa37f2
to
f7e8115
Compare
LGTM |
f7e8115
to
365c3b8
Compare
Description:
This PR adds structured logging support for lambda functions.
Log format and level now encoded into message header.
User are able to set format and level through the environment variables
AWS_LAMBDA_LOG_FORMAT
andAWS_LAMBDA_LOG_LEVEL
.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.