How to use logger to print a list in just one line in Python

Question:

I want to print a list using logging in only one line in Python 3.6. Currently my code looks like this.

logger = logging.getLogger()
logger.setLevel(log_level)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(log_level)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)

# some codes in-between

num_list = [1, 2, 3, 4, 5]
logger.info("Numbers in num_list are: ")
for item in num_list:
    logger.info(item)

What I want to get is

2018-07-23 17:29:30,200 - root - INFO - Numbers in num_list are: 1 2 3 4 5

However, I would get

2018-07-23 17:29:30,200 - root - INFO - Numbers in num_list are:
2018-07-23 17:29:30,200 - root - INFO - 1
2018-07-23 17:29:30,200 - root - INFO - 2
2018-07-23 17:29:30,200 - root - INFO - 3
2018-07-23 17:29:30,200 - root - INFO - 4
2018-07-23 17:29:30,200 - root - INFO - 5

I know if I used print to output, I could have use print(item, end=" ") to explicitly change what follows the output. However, it seems that the logging won’t accept end as an input parameter.

Does anyone have any ideas how I could get the desired output? Many thanks!

Asked By: Jekyll SONG

||

Answers:

You are using a for loop which iterates over all of your list and logs it one by one try: logger.info("Numbers in num_list are: {}".format(' '.join(map(str, num_list)))) to post them all in once

See: https://docs.python.org/3/library/stdtypes.html?highlight=str#str.join

Answered By: Jorkermc

There is a strong difference between an output stream (used by print) and a message log. The stream is a sequence of characters (or bytes for binary strings) that may happen to contain newline characters. Those newlines characters are then interpreted when you display them on a terminal (or when you print them).

A log is a sequence of messages, and each message is supposed to be atomic. Once a message has been logged, you cannot add anything to it but can only log new messages.

So you have to first fully build your message and then log it:

num_list = [1, 2, 3, 4, 5]
msg = "Numbers in num_list are: " + " ".join(num_list)      # fully build the message
logger.info(msg)                                            #  and then log it
Answered By: Serge Ballesta

Not exactly what you want, but more lazy:
It may be usefull when you want to create some fast debug:

num_list = [1, 2, 3, 4, 5]
logger.info(str(("Numbers in num_list are: ",num_list))

output:

('Numbers in num_list are: ', [1, 2, 3, 4, 5])
Answered By: Ludo Schmidt

Another nice short way I’ve found here

nums = [3, 87, 28, 25, 96]
logging.debug("Here are my favourite numbers: " + str(nums)[1:-1])

"""
 Output:
 Here are my favorite numbers: 3, 87, 28, 25, 96
""" 

One more using map

logging.debug("Favourite numbers are: ".join(map(str, nums))
Answered By: Razib

One great feature of Python logging is that (potentially expensive) string concatenation is only performed if the logging level means the message is to be output.

However, in the following code, the str(), map() and join() are always performed and then the result is possibly just discarded if the logging level is above debug:

logging.debug("Favourite numbers are: ".join(map(str, nums))

I created a small class which allows for intuitive one-liner logging of sequences like so:

logging.debug("Favourite numbers are: %s", rstr(nums))

Where the rstr class is defined as follows:

class rstr:
    """Wrapper to recursively str()ise a list or tuple or set. The work is only
    performed in the __str__ call so these objects 'cost' very little if they
    are never actually used (e.g. as in a logger.debug() argument when the
    message is never output."""
    
    def __init__(self, seq):
        """Creates an rstr instance which will string-ise the argument when
        called."""
        self._seq = seq
    
    def __str__(self):
        """String-ise and return the argument passed to the constructor."""
        if isinstance(self._seq, list):
            return "[" + self._str_items() + "]"
        elif isinstance(self._seq, tuple):
            return "(" + self._str_items() + ")"
        elif isinstance(self._seq, set):
            return "{" + self._str_items() + "}"
        else:
            return str(self._seq)
    
    def _str_items(self):
        """Returns the string-ised forms of the items in the argument passed to
        the constructor - no start/end brackets/braces."""
        return ", ".join(map(str, self._seq))
    

This could obviously be made completely recursive if desired, but the simpler version is shown here for clarity.

Answered By: gladd

You can pass format strings and args to logging.

The debug() docs use this example:

d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
logging.warning('Protocol problem: %s', 'connection reset', extra=d)

For your case, you can can pass the num_list to the format string as a string and it will pretty print it for you.

>>> num_list = [1, 2, 3, 4, 5, ]
>>> logger.info("Numbers in num_list are: %s", num_list)
INFO: Numbers in num_list are: [1, 2, 3, 4, 5]

>>> num_list = [1, 2, 3, 4, 5, [44,454,54], { "aa": 234} ]
>>> logger.info("Complex example: %s", num_list)
INFO: Complex example: [1, 2, 3, 4, 5, [44, 454, 54], {'aa': 234}]

As @Maico Timmerman pointed out:

It is better to let the logging module do the actual formatting using the %-syntax, since the message might not be printed at all.

Answered By: idbrii
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.