Created
April 2, 2019 11:14
-
-
Save betatim/60c88eafc387872e50e4b6f7fd06840a to your computer and use it in GitHub Desktop.
Can we inject arguments from the caller's locals into callees? Yes! Run `python injector.py`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import inspect | |
| import logging | |
| from functools import wraps | |
| def inject_logger(method): | |
| @wraps(method) | |
| def wrapper(*args, **kwargs): | |
| frame = inspect.currentframe() | |
| try: | |
| frames = inspect.getouterframes(frame) | |
| callers_locals = frames[-2].frame.f_locals | |
| # could make this more robust ... | |
| return method(*args, logger=callers_locals['self'].logger, **kwargs) | |
| finally: | |
| del frame | |
| del frames | |
| return wrapper | |
| class External: | |
| def __init__(self): | |
| self.a = 'a' | |
| @inject_logger | |
| def do_it(self, b, logger=None): | |
| if logger is None: | |
| logger = logging.getLogger('external') | |
| logger.error("%s is %s", self.a, b) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from external import External | |
| import logging | |
| # connfigure some bespoke logger that we want to use | |
| logger = logging.getLogger() | |
| fmt = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s') | |
| ch = logging.StreamHandler() | |
| ch.setFormatter(fmt) | |
| logger.addHandler(ch) | |
| class IDAdapter(logging.LoggerAdapter): | |
| def process(self, msg, kwargs): | |
| return "[%s] %s" % (self.extra["id"], msg), kwargs | |
| class Internal: | |
| def __init__(self, n): | |
| # some value that changes in every instance, maybe a unique ID for this | |
| # request and we use an adaptor to pass that value into our logger | |
| # without having to add it to every `logger.log()` call | |
| self.n = n | |
| self.logger = IDAdapter(logger, {'id': n}) | |
| def do_something(self): | |
| # because `do_it` is decorated by `inject_logger` we don't have to | |
| # explicitly pass in our logger, saving us some typing and we won't | |
| # forget to do it. This means we don't have to write: | |
| # ext.do_it(self.n, logger=self.logger) | |
| # but can get away with the simpler: | |
| ext.do_it(self.n) | |
| # some kind of external service we instantiate once | |
| ext = External() | |
| int = Internal(1) | |
| int.do_something() | |
| int = Internal(2) | |
| int.do_something() | |
| int = Internal(3) | |
| int.do_something() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment