pankmish
pankmish

Reputation: 847

How to print multiline logs using python logging module?

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

Upvotes: 2

Views: 4604

Answers (4)

studioj
studioj

Reputation: 1390

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')

Upvotes: 1

macetw
macetw

Reputation: 1820

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.

Upvotes: 0

Qeek
Qeek

Reputation: 1970

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')

Upvotes: 6

ssm
ssm

Reputation: 5373

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 ...

Upvotes: 2

Related Questions