how to indent multiline message printed by python logger?
Asked Answered
L

4

11

Current behaviour:

DEBUG:package:123 > message with
multiple lines
foo bar

Wanted behaviour:

DEBUG:package:123 > message with
                    multiple lines
                    foo bar

DEBUG:package:123 can have different width so it's not possible to adjust message before sending it to logger.

Lashanda answered 28/10, 2019 at 12:11 Comment(2)
how are you logging these lines? are you logging them as 3 seperate lines?Bolyard
It seems like the easiest thing to do would be to start the message with a \r\n so all the desired aligned elements are left-aligned. Not exactly the requested functionality, but maybe accomplishes the goal very simply.Wrath
V
9

Existing answers answered the indentation part well. But the new format implementation did not handle other fields (e.g. exec_info, stack_info) properly compared to the original logging.Formatter.

The below implementation is based on simonzack's answer and reuses the original formatter to minimize the side effects.

[edit 2021-11] it also handles formatted values correctly by post-processing the formatted message instead of pre-processing it

class MultiLineFormatter(logging.Formatter):
    """Multi-line formatter."""
    def get_header_length(self, record):
        """Get the header length of a given record."""
        return len(super().format(logging.LogRecord(
            name=record.name,
            level=record.levelno,
            pathname=record.pathname,
            lineno=record.lineno,
            msg='', args=(), exc_info=None
        )))

    def format(self, record):
        """Format a record with added indentation."""
        indent = ' ' * self.get_header_length(record)
        head, *trailing = super().format(record).splitlines(True)
        return head + ''.join(indent + line for line in trailing)
Vigilance answered 29/3, 2021 at 13:26 Comment(0)
S
3

Here's is aiven's version which is more modernized and flexible.

class MultiLineFormatter(logging.Formatter):
    def format(self, record):
        message = record.msg
        record.msg = ''
        header = super().format(record)
        msg = textwrap.indent(message, ' ' * len(header)).lstrip()
        record.msg = message
        return header + msg

In order to use this:

formatter = MultiLineFormatter(
    fmt='%(asctime)s %(levelname)-8s %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
)
log_handler = logging.StreamHandler()
log_handler.setFormatter(formatter)
Sempach answered 15/3, 2021 at 12:44 Comment(0)
L
2

End up with custom formatter without message in fmt string:

import textwrap

class Formatter(logging.Formatter):
    def __init__(self):
        super(Formatter, self).__init__(fmt="%(levelname)-8s %(name)20s:%(lineno)-3d > ")

    def format(self, record):
        header = super(Formatter, self).format(record)
        msg = textwrap.indent(record.message, ' ' * len(header)).strip()
        return header + msg
Lashanda answered 28/10, 2019 at 12:37 Comment(0)
L
0

Modified aiven's version to work properly.

class Formatter(logging.Formatter):
    def __init__(self, format_str):
        super(Formatter, self).__init__(fmt=format_str)

    def format(self, record):
        message = record.msg
        record.msg = ''
        header = super(Formatter, self).format(record)
        msg = textwrap.indent(message, ' ' * len(header)).strip()
        record.msg = message
        return header + msg
Loyal answered 16/7, 2020 at 3:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.