powertools-lambda-python icon indicating copy to clipboard operation
powertools-lambda-python copied to clipboard

Feature request: Implement a context manager to bind variables to a logger

Open wesleyhamburger-okta opened this issue 2 years ago • 3 comments

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

wesleyhamburger-okta avatar Mar 28 '23 23:03 wesleyhamburger-okta

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

boring-cyborg[bot] avatar Mar 28 '23 23:03 boring-cyborg[bot]

hey @wesleyhamburger-okta thanks for raising this feature request. Please allow me to ask some questions to make sure I understand it correctly:

  1. Are you also intending to use with threads? see https://github.com/awslabs/aws-lambda-powertools-python/issues/991
  2. 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.,

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 .bind that yields back a new isolated Logger instance while retaining the service etc.

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()

heitorlessa avatar Mar 31 '23 11:03 heitorlessa

Related #3719

trallnag avatar Jul 02 '24 11:07 trallnag