Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter stdout in python logging

I'm using library 'logging' to log info and warning messages in my scripts, is there anyway I can filter out passwords (I have more than one passwords and would like to get them replaced with asterisks) while printing into stdout?

like image 386
MikA Avatar asked Jan 20 '16 16:01

MikA


2 Answers

In order to filter out specific words contained in your password list from the stdout stream (that's where logging.DEBUG and logging.INFO messages go) and the stderr stream (that's where logging.WARNING, logging.ERROR and logging.CRITICAL messages go), you can replace the original streams with a simple class that replaces the critical words before writing them out:

class PasswordFilter(object):
    def __init__(self, strings_to_filter, stream):
        self.stream = stream
        self.strings_to_filter = strings_to_filter

    def __getattr__(self, attr_name):
        return getattr(self.stream, attr_name)

    def write(self, data):
        for string in self.strings_to_filter:
            data = re.sub(r'\b{0}\b'.format(string), '*' * len(string), data)
        self.stream.write(data)
        self.stream.flush()

    def flush(self):
        self.stream.flush()

Replace the original streams with the filtered ones like that:

top_secret_passwords = ['do not tell me', 'I am secret', 'important', 'foo',
                        'foobar']
sys.stdout = PasswordFilter(top_secret_passwords, sys.stdout)
sys.stderr = PasswordFilter(top_secret_passwords, sys.stderr)

Now, set up logging and write some log messages:

# set up your logging after activating the filter, won't work otherwise
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

logger.debug('You cannot see me anymore: {0}'.format(top_secret_passwords[0]))
logger.info('You cannot see me anymore: {0}'.format(top_secret_passwords[1]))
logger.warning('You cannot see me anymore: {0}'.format(top_secret_passwords[2]))
logger.error('You cannot see me anymore: {0}'.format(top_secret_passwords[3]))
logger.critical('You cannot see me anymore: {0}'.format(top_secret_passwords[4]))

The output will look like this:

DEBUG:__main__:You cannot see me anymore: **************
INFO:__main__:You cannot see me anymore: ***********
WARNING:__main__:You cannot see me anymore: *********
ERROR:__main__:You cannot see me anymore: ***
CRITICAL:__main__:You cannot see me anymore: ******
like image 148
Dirk Avatar answered Sep 19 '22 18:09

Dirk


Filter out patterns, without printing a newline

A modification to @Dirk's Filter class.

This version will not print out any line which matches the input pattern. It will also not print out a newline break after a filtered line has been skipped

class Filter(object):
    def __init__(self, stream, re_pattern):
        self.stream = stream
        self.pattern = re.compile(re_pattern) if isinstance(re_pattern, str) else re_pattern
        self.triggered = False

    def __getattr__(self, attr_name):
        return getattr(self.stream, attr_name)

    def write(self, data):
        if data == '\n' and self.triggered:
            self.triggered = False
        else:
            if self.pattern.search(data) is None:
                self.stream.write(data)
                self.stream.flush()
            else:
                # caught bad pattern
                self.triggered = True

    def flush(self):
        self.stream.flush()

# example
sys.stdout = Filter(sys.stdout, r'Read -1')  # filter out any line which contains "Read -1" in it


# No lines (or newline breaks) will be printed to stdout after running the below.
for _ in range(10):
  print('Read -1 expected 4096')
like image 30
Bobs Burgers Avatar answered Sep 19 '22 18:09

Bobs Burgers