import logging as _logging from syslogformat.helpers import check_level, py_to_sys_lvl class SyslogFormatter(_logging.Formatter): """ logging.Formatter subclass for doing three things: 1) Format exception log messages into one-liners, 2) prepend a syslog oriented log level number to be recognized by systemd, and 3) append more details ([module].[function].[line]) to every message, when specified level is exceeded. """ def __init__(self, *args, **kwargs): self.detail_threshold = check_level(kwargs.pop('detail_threshold', _logging.WARNING)) self.prepend_lvl_name = check_level(kwargs.pop('prepend_lvl_name', True)) super().__init__(*args, **kwargs) def formatException(self, exc_info) -> str: """Format an exception so that it prints on a single line.""" return repr(super().formatException(exc_info)) def format(self, record: _logging.LogRecord) -> str: """ Format to be compatible with syslog levels and replace the newlines. The entire message format is hard-coded here, so no format needs to be specified in the usual config. """ record.message = record.getMessage() # Prepend syslog level depending on record level s = f"<{py_to_sys_lvl(record.levelno)}>" if self.prepend_lvl_name: s += f"{record.levelname:<8} | " s += self.formatMessage(record) # If record level exceeds the threshold, append additional details if record.levelno >= self.detail_threshold: s += f" | {record.module}.{record.funcName}.{record.lineno}" if record.exc_info and not record.exc_text: record.exc_text = self.formatException(record.exc_info) if record.exc_text: s += ' | ' + record.exc_text if record.stack_info: s += self.formatStack(record.stack_info) # Reformat exception line return s.replace("\n", "") if __name__ == '__main__': log = _logging.getLogger() hdl = _logging.StreamHandler() fmt = SyslogFormatter() hdl.setFormatter(fmt) log.addHandler(hdl) log.setLevel(_logging.NOTSET) log.debug("foo") log.info("bar") log.warning("baz") try: raise ValueError("this is bad") except ValueError as e: log.exception("oh no")