#!/usr/bin/env python # encoding: utf-8 from pprint import pformat, pprint import logging class PasswordMaskingFilter(logging.Filter): """Demonstrate how to filter sensitive data:""" def filter(self, record): # The call signature matches string interpolation: args can be a tuple or a lone dict if isinstance(record.args, dict): record.args = self.sanitize_dict(record.args) else: record.args = tuple(self.sanitize_dict(i) for i in record.args) return True @staticmethod def sanitize_dict(d): if not isinstance(d, dict): return d if any(i for i in d.keys() if 'password' in i): d = d.copy() # Ensure that we won't clobber anything critical for k, v in d.items(): if 'password' in k: d[k] = '*** PASSWORD ***' return d class CustomFormatter(logging.Formatter): def format(self, record): res = super(CustomFormatter, self).format(record) if hasattr(record, 'request'): filtered_request = PasswordMaskingFilter.sanitize_dict(record.request) res += '\n\t' + pformat(filtered_request, indent=4).replace('\n', '\n\t') return res logging.basicConfig(level=logging.INFO) logger = logging.getLogger() logger.addFilter(PasswordMaskingFilter()) for handler in logger.root.handlers: # This is lazy and does only the minimum alteration necessary. It'd be better to use # dictConfig / fileConfig to specify the full desired configuration from the start: # http://docs.python.org/2/library/logging.config.html#dictionary-schema-details handler.setFormatter(CustomFormatter(handler.formatter._fmt)) logging.info('This is a simple message') fake_request = {'path': '/foo/bar/', 'method': 'GET', 'username': 'test_user', 'password': 's00p3r s3kr1t'} logging.info('basic request request: %(method)s %(path)s', fake_request) logging.info('Dumped request: %r', fake_request) logging.info('extra request: %(method)s %(path)s', fake_request, extra={'request': fake_request})