I use the below code to include context specific information in logging record (see obj).
import logging
def setup_logging():
logger = logging.getLogger('sample')
logger.setLevel(logging.INFO)
f = logging.Formatter(
"%(asctime)s | %(levelname)-7s | %(funcName)-10s | %(obj)-20s | %(message)s")
s = logging.StreamHandler()
s.setFormatter(f)
logger.addHandler(s)
logger.addFilter(ContextFilter())
return logger
class ContextFilter(logging.Filter):
def filter(self, record):
record.obj = getattr(record, "obj", "")
return True
class ContextLogger:
def __init__(self, logger, obj):
self.logger = logger
self.obj = obj
def log(self, msg, level=logging.INFO):
log_func = {
logging.INFO: self.logger.info,
logging.WARN: self.logger.warn,
logging.ERROR: self.logger.error,
logging.DEBUG: self.logger.debug,
}
log_func[level](msg, extra={"obj": self.obj})
def info(self, msg):
self.log(msg, logging.INFO)
def error(self, msg):
self.log(msg, logging.ERROR)
def warn(self, msg):
self.log(msg, logging.WARN)
def debug(self, msg):
self.log(msg, logging.DEBUG)
The information is recorded in each class via __repr__ dunder method.
Example
class B:
def __init__(self, a, b, logger):
self.a = a
self.b = b
self.logger = logger
self.logger_ = ContextLogger(logger, self)
self.logger.info('plain info')
self.logger_.info('context info')
def __repr__(self):
return f"a: {self.a}"
def method(self):
try:
return float('a')
except Exception as e:
self.logger_.error(e)
def f(logger):
b1 = B('Joe', 48, logger)
b1.method()
def start():
logger = setup_logging()
logger.info('here')
f(logger)
start()
Output
2023-08-08 13:13:02,494 | INFO | start | | here
2023-08-08 13:13:02,499 | INFO | __init__ | | plain info
2023-08-08 13:13:02,500 | INFO | log | a: Joe | context info
2023-08-08 13:13:02,503 | ERROR | log | a: Joe | could not convert string to float: 'a'
I am not particularly convinced about
- need for logging level methods inside ContextLogger
- need for separate classes for ContextLogger and ContextFilter
- cost of initializing ContextLogger with each class instance
- occupying
__repr__for the purpose of context - no support for ad hoc context value (e.g.
logging.info('msg', 'alternative value'))