1

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

1

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.

1

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)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.