powertools-lambda-python
powertools-lambda-python copied to clipboard
Feature request: Implement a context manager to bind variables to a logger
Use case
I wan't to add variables to the logger for the duration of a request and get clean up for free without needing to explicitly call delete.
This is similar to https://www.structlog.org/en/stable/api.html#structlog.contextvars.bound_contextvars
Solution/User Experience
@contextmanager
def bind(self, **args):
logger_instance.append_keys(**args)
yield logger_instance
logger_instance.remove_keys(args.keys())
with logger.bind(user_id=123):
perform_operation():
Alternative solutions
No response
Acknowledgment
- [X] This feature request meets Lambda Powertools Tenets
- [ ] Should this be considered in other Lambda Powertools languages? i.e. Java, TypeScript
Thanks for opening your first issue here! We'll come back to you as soon as we can. In the meantime, check out the #python channel on our AWS Lambda Powertools Discord: Invite link
hey @wesleyhamburger-okta thanks for raising this feature request. Please allow me to ask some questions to make sure I understand it correctly:
- Are you also intending to use with threads? see https://github.com/awslabs/aws-lambda-powertools-python/issues/991
- Are you simply looking for a shortcut to cut boilerplate? OR, a completely isolated logger instance within that operation?
- 2A. If the latter, would you want existing keys to be carried over when you use
bind()? e.g., lambda request id, etc.,
- 2A. If the latter, would you want existing keys to be carried over when you use
Reason I ask that is structlog.bind() returns an entirely new logger instance.
stateDiagram
[*] --> Logger
Logger --> new_logger
new_logger --> append(user_id)
append(user_id) --> perform_operation()
perform_operation() --> remove(user_id)
new_logger --> [*]
We, on the other hand, would append user_id to the registered Formatter state, and that isn't thread-safe (yet!).
stateDiagram
[*] --> Logger
Logger --> append(user_id)
append(user_id) --> perform_operation()
perform_operation() --> remove(user_id)
remove(user_id) --> [*]
[*] --> LoggerElsewhere
LoggerElsewhere --> bi_directional_state
LoggerElsewhere --> receives_user_id_too
receives_user_id_too --> user_id_removed
user_id_removed --> [*]
The former would need a better name to not confuse existing structlog cystiners, since they would be led to believe our with logger.bind would yield a completely isolated state.
Looking forward to hearing from you.
PS: If it's primarily boilerplate, we'd welcome a contribution and can guide/co-develop you all the way through to expedite this for the next release :)
Thanks a lot!!
proof of concept for a
.bindthat yields back a new isolated Logger instance while retaining theserviceetc.
Example for question 2
with logger.bind(user_id="123") as new_logger:
# new_logger.info() would have `user_id` key... but if I do logger.info()... `user_id` would not be present
# similarly, perform_operation() OR any other function with a `logger` instance wouldn't have `user_id`
perform_operation()
Related #3719