2

I want to create multiline logger using python logging module. When I use the code snippet below to format logger:

import logging
logger = logging.getLogger(file_name)
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)

I get the output below for multiline logs when I use logger.info("""line 1\nline 2\n line 3""")

2017-07-20 13:21:14,754 - my_logger.py - INFO - line 1
line 2
line 3

I want my output as below:

2017-07-20 13:21:14,754 - my_logger.py - INFO - line 1
2017-07-20 13:21:14,754 - my_logger.py - INFO - line 2
2017-07-20 13:21:14,754 - my_logger.py - INFO - line 3
2
  • Is there a reason you can't have multiple calls to logger.info()? Commented Jul 20, 2017 at 13:57
  • @Jakub i want to create a logger for a framework and want to have this kind of formatter. Commented Jul 20, 2017 at 14:00

4 Answers 4

6

You can create your own formatter:

import logging
class MultilineFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord):
        save_msg = record.msg
        output = ""
        for line in save_msg.splitlines():
            record.msg = line
            output += super().format(record) + "\n"
        record.msg = save_msg
        record.message = output
        return output

Than modify your logger initialization as follows:

formatter = MultilineFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
Sign up to request clarification or add additional context in comments.

2 Comments

warning over here! test_logger.info("foo %s \nfoo %s", "bar", "bar") if you do multi line % replacement this wont work. I'll look for an extension to this answer
added mine below
2

Short answer, you cannot. What you can do is the following:

logger.info('line 1')
logger.info('line 2')
logger.info('line 3')

Or if you have multiline stuff, you can do:

map(logger.info, 'line 1\nline 2\nline 3'.split('\n'))

These won't show up at the same time, but will be very close ...

1 Comment

Do note that the latter only works on py2 due to map being lazy; you can do [logger.info(line) for line in 'line 1\nline 2\nline 3'.splitlines()] on py3.
1

adjusting @Qeek 's answer so we can support % replacement in all lines

test_logger.info("foo %s foo %s \nfoo %s", "bar","bar", "bar")    

taking into account these characters which can be added to % diouxXeEfFgGcrsa

https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting

import logging
import re

class MultilineFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord):
        save_msg = record.msg
        args = list(record.args)
        output = ""
        for line in save_msg.splitlines():
            record.msg = line
            number_of_string_subs = len(re.findall(r"%[diouxXeEfFgGcrsa]", line))
            record.args = tuple(args[:number_of_string_subs])
            args = args[number_of_string_subs:]
            output += super().format(record) + "\n"
        record.msg = save_msg
        record.message = output
        return output

Than modify your logger initialization as follows:

formatter = MultilineFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

Comments

0

If I am only using a single log level (warning or info or similar), I would prefer using a helper function to do this:

def multiline_warning(header, body):
  for line in body.splitlines():
    logging.warning(f"{header}: {line}")

And in the context, just call:

multiline_warning("stderr", subprocess_result.stderr)

If using more than just one of (CRITICAL, ERROR, WARNING, INFO or DEBUG), you might abstract the log-level and pass it as a parameter, and use logging.log instead.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.