Reputation: 847
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
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
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
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
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