-
Notifications
You must be signed in to change notification settings - Fork 70
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
feat: replace standard logging with structured logging #122
base: main
Are you sure you want to change the base?
Conversation
logger = logging.getLogger() | ||
logger.setLevel(logging.INFO) | ||
logging.getLogger('boto3').setLevel(logging.CRITICAL) | ||
logging.getLogger('botocore').setLevel(logging.CRITICAL) |
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 found this guide with regards to working with the standard library, but I wasn't sure how I could make this applicable here. Otherwise, third party deps will continue using the standard logger unless someone can figure out if wrapping the logger
is possible.
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.
My colleague suggests that we could use https://nhairs.github.io/python-json-logger/latest/ as the formatter for these handlers. e.g. something like:
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logging.getLogger('boto3').addHandler(handler)
logging.getLogger('botocore').addHandler(handler)
Do you think this would work?
# use structlog's production-ready, performant example config | ||
# ref: https://www.structlog.org/en/stable/performance.html#example |
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.
Talking to this particular config, when I ran into the example in this document, it matched what I had in my head as a starting point. The company I work at have some opinions on things, though I wasn't going to impose them here.
structlog.processors.add_log_level, | ||
structlog.processors.format_exc_info, | ||
structlog.processors.TimeStamper(fmt="iso", utc=True), | ||
structlog.processors.EventRenamer("message"), |
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.
By default, this value is "event" though that term seems to be loaded since the Lambda accepts "events" and I thought that maybe message
would be a good compromise. Though I am wary of long-term implications of this choice.
Either way, I don't have a strong opinion on whether it stays as this, we leave it as the default event
, or we use a different name.
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.
message
works fine for me. 👍
functions/replace-route/app.py
Outdated
slogger.error("Unknown event", eventPayload=event) | ||
return | ||
|
||
if event.get("source") != "aws.events": | ||
logger.error(f"Unable to handle unknown event type: {json.dumps(event)}") | ||
slogger.error("Unable to handle unknown event type", eventPayload=json.dumps(event)) |
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.
As the diff indicates, this changed from a string interpolation to a key. As this comment alludes to, the term event
is loaded.
I am inclined to drop this key and keep the string interpolation like I did everywhere else, or do a combination of both, or even leave that decision to another PR.
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 decided to preserve the string interpolation in a later commit and will leave changes to the log message contents themselves to a later PR.
This changes removes the addition of the eventPayload key that was imposed in the previous commit. I would rather leave the decision to the maintainers. Whether it be reverting *this* commit, or accepting this one, or leaving it to the future PR.
Thank you for the PR! I am a bit busy atm, but I hope to review & discuss in the coming days. Appreciate the patience! |
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.
Left a couple comments/ideas to consider.
Mind taking a peek at test_replace_route.py
and adding a unit test?
@@ -120,10 +137,10 @@ def replace_route(route_table_id, nat_gateway_id): | |||
"RouteTableId": route_table_id | |||
} | |||
try: | |||
logger.info("Replacing existing route %s for route table %s", route_table_id, new_route_table) | |||
slogger.info("Replacing existing route %s for route table %s", route_table_id, new_route_table) |
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 line prints a dictionary (new_route_table
), which would end up nested in json. The message here is sorta broken - "existing route {route_table_id}" is wrong, and then it prints the whole route table.
Let's update the message so it's more accurate and doesn't include a nested dictionary. It will print more cleanly in the output. It does break backward compatibility for folks monitoring for this specific phrasing, which is too bad, but worthwhile to fix. May as well do it now.
slogger.info("Replacing existing route %s for route table %s", route_table_id, new_route_table) | |
slogger.info("Updating route table %s to use NAT Gateway ID %s", route_table_id, nat_gateway_id) |
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.
Also, most folks should be monitoring for ERROR
status or the "Failed connectivity tests! Replacing route" message. This one is INFO
.
logger.error(f"Unknown event: {event}") | ||
slogger.error("Unknown event: %s", {event}) |
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.
Thanks for standardizing this throughout the file.
structlog.processors.add_log_level, | ||
structlog.processors.format_exc_info, | ||
structlog.processors.TimeStamper(fmt="iso", utc=True), | ||
structlog.processors.EventRenamer("message"), |
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.
message
works fine for me. 👍
logger_factory=structlog.BytesLoggerFactory() | ||
) | ||
|
||
# logger is still needed to set the level for dependencies | ||
logger = logging.getLogger() |
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 this line still needed?
logger = logging.getLogger() | ||
logger.setLevel(logging.INFO) | ||
logging.getLogger('boto3').setLevel(logging.CRITICAL) | ||
logging.getLogger('botocore').setLevel(logging.CRITICAL) |
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.
My colleague suggests that we could use https://nhairs.github.io/python-json-logger/latest/ as the formatter for these handlers. e.g. something like:
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logging.getLogger('boto3').addHandler(handler)
logging.getLogger('botocore').addHandler(handler)
Do you think this would work?
Purpose 🎯
These changes replace the standard library logging with structured logging using structlog. Structured logging provides easier-to-parse capabilities for toolings and humans when going through logs.
Context 🧠
bad events/all valid events
, and right nowaws-alternat
exercises a kind of "no news is good news" reporting.Notes 📓
structlog
structlog