For simplicity, assume my application logs only dictionaries. I want to add a step to Python logging for my application to prevent logging any dictionary with the key password
, i.e.,
def clean_log(blob):
if 'password' in blob:
blob['password'] = 'REDACTED'
return blob
One thing I could do is put clean_log
in its own file clean_log.py
, import that in all my other files that call the logger, then add it into the function call, e.g.,
import logging
import clean_log
LOGGER = logging.getLogger()
def process(event):
LOGGER.info(clean_log.clean_log(event))
return event
Is there a nicer way to do this? It would be cool if I could overwrite getLogger
somehow so that anytime logging.getLogger
is called in the source code, it could return a modified logger that just knows to clean_logs
first. For example
import logging
import clean_log
class MyLogger(logging.Logger):
def info(self, blob):
return super().info(clean_log.clean_log(blob))
Is there a way to always just get this logger in the source code from something like getLogger
, using handlers
or filters
or something?
Its not totally clear to me if this is a good idea, but I thought it would be an educational experience to try to find some kind of optimal/Pythonic way to do this. I can't be the first one to want to do this.
2 Answers 2
There is no good way to do this in Python 3.7 or less. However, since Python 3.8, you can create a wrapper that looks like this:
import logging
import clean_log
LOGGER = logging.getLogger()
def my_info(msg):
return LOGGER.info(clean_log.clean_log(blob), stacklevel=2)
This wrapper would still give you stack info on where my_info
was called. Without that stacklevel
arg, it would look like all the logs come from the wrapper, which defeats many of the good features of the logging
module.
This is exactly what setLoggerClass is for.
E.g. I often use this snippet to allow lazy evaluation of parameters:
import logging
class LazyLogger(logging.getLoggerClass()):
def _log(self, level, msg, args, **kwargs):
def maybe_callable(x):
return x() if callable(x) else x
super()._log(
level,
maybe_callable(msg),
tuple(maybe_callable(i) for i in args),
**kwargs
)
logging.setLoggerClass(LazyLogger)