This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Make it easy to replace print() calls with logging calls
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.5
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Saimadhav.Heblikar, barry, numerodix, pitrou, rhettinger, vinay.sajip
Priority: normal Keywords:

Created on 2014-08-14 15:18 by pitrou, last changed 2022-04-11 14:58 by admin.

Messages (11)
msg225302 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-08-14 15:17
Often an application (or e.g. a library's test suite), in its early development, will start making print() calls, and later will want to switch to the logging module. However the logging module doesn't offer any facility for this: you can't print() to a logger object, and loggers don't have a method that reflects print()'s signature.

(note print() only uses the .write() and .flush() methods on its stream argument, so a simple wrapper may make the trick)
msg225324 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2014-08-14 23:25
This is easy enough to do, I posted about it in 2009 here:

http://plumberjack.blogspot.co.uk/2009/09/how-to-treat-logger-like-output-stream.html

Basically, something like

class LoggerWriter(object):
    def __init__(self, logger, level):
        self.logger = logger
        self.level = level

    def write(self, message):
        if message != '\n':
            self.logger.log(self.level, message)

    def flush(self):
        pass

I'm not sure something like this really needs to be in the stdlib.

Loggers shouldn't have a method with a print-like signature, since it's Handlers that are concerned with output destinations such as streams.
msg225329 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-08-15 02:53
> This is easy enough to do, I posted about it in 2009 here

I know it's easy. It's still annoying to have to write such boilerplate by hand (especially when everyone ends up rewriting the exact same one).
msg225337 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2014-08-15 05:14
This is a recurring problem.  I concur with Antoine that there needs to be logging functions that are harmonized with print().
msg225351 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2014-08-15 14:07
Here's a tentative suggestion. Does it meet your needs?

import logging
import sys

class LoggerWriter(object):
    def __init__(self, logger='', level=logging.DEBUG):
        if isinstance(logger, str):
            logger = logging.getLogger(logger)
        self.logger = logger
        self.level = level
        self.buffer = ''

    def _output(self, force):
        lines = self.buffer.split('\n')
        if force:
            self.buffer = ''
        else:
            self.buffer = lines.pop()
        for line in lines:
            self.logger.log(self.level, line)

    def flush(self):
        self._output(True)

    def write(self, text):
        self.buffer += text
        self._output(False)


def main():
    stream = LoggerWriter()
    with open('lwtest.txt', 'w') as f:
        print('foo', 1, 'bar\n\n', 2.0, 'baz', file=stream)
        print(file=stream)
        print('foo', 1, 'bar\n\n', 2.0, 'baz', file=f)
        print(file=f)
        print('frob', end=' ', file=stream)
        print('frob', end=' ', file=f)
        f.flush()
    stream.flush()

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s',
                        filename='lwtest.log')
    sys.exit(main())
msg225408 - (view) Author: Martin Matusiak (numerodix) * Date: 2014-08-16 19:07
Hm, on this model you still have to go and update all your print() statements with the file= argument. That's less invasive than replacing them with logger.info(), but still not very elegant. You're also repeating the stream on every occurrence, so easy to leave it out by mistake.
msg225412 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2014-08-16 19:58
> but still not very elegant. You're also repeating the stream on every occurrence, so easy to leave it out by mistake.

It might need more work to make a LoggerWriter work with sys.stdout [i.e. allowing sys.stdout = LoggerWriter()], which seems to be the only way to avoid passing a file= each time. I'm not sure that write() and flush() would be sufficient for this - for example, an encoding attribute might be required. Feel free to suggest improvements.
msg225445 - (view) Author: Martin Matusiak (numerodix) * Date: 2014-08-17 14:19
Would it make sense for the logging module to supply something like the LoggerWriter class you wrote? 

With a section in the documentation that says something like "have you been logging using print() this whole time? No problem, just do sys.stdout = LoggerWriter()"?
msg225456 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2014-08-17 17:53
> Would it make sense for the logging module to supply something like the LoggerWriter class you wrote? 

Perhaps I'm not making myself clear :-( I've posted the LoggerWriter class (not the initial version, but the later, slightly more functional one) to see if it will meet the needs that Antoine raised and Raymond concurred with. The intention is, if it meets their requirements, to indeed add the LoggerWriter class to the stdlib. If it doesn't meet the requirements, then suggestions and/or patches to improve it are welcome.

However, from a smoke test, sys.stdout = LoggerWriter() doesn't seem to work as expected, but I can't see why at the moment (and currently I'm not able to give it my undivided attention).
msg225474 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-08-17 22:28
> However, from a smoke test, sys.stdout = LoggerWriter() doesn't seem to work as expected

I wasn't thinking about replacing sys.stdout (which I think is in general a bad pattern, except for daemonized processes), rather being able to 1) either replace the print() calls with logging calls with a similar API or 2) add a file= argument to print calls.

(the 1) solution could look like "print = logging.getLogger('foo').debug_printer()")
msg225477 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2014-08-17 23:21
Loggers don't deal with output - handlers do - so I would prefer not to add an output-related method to a logger: people confuse the two enough as it is. Note that 

stream = LoggerWriter('foo')

gives an equivalent result to

stream = getLogger('foo').debug_printer()

and in my view the former is preferable.

I agree that there is no compelling reason to replace sys.stdout, though it means that you have to pass file=stream in each print() call.

It also makes more sense to use print() rather than have a separate API that looks just like print(), as you essentially would have to duplicate the functionality of the print() API and track enhancements to it over time (to avoid surprises).
History
Date User Action Args
2022-04-11 14:58:06adminsetgithub: 66391
2019-04-26 20:28:42BreamoreBoysetnosy: - BreamoreBoy
2014-08-17 23:21:13vinay.sajipsetmessages: + msg225477
2014-08-17 22:28:32pitrousetmessages: + msg225474
2014-08-17 17:53:23vinay.sajipsetmessages: + msg225456
2014-08-17 14:19:50numerodixsetmessages: + msg225445
2014-08-16 19:58:14vinay.sajipsetmessages: + msg225412
2014-08-16 19:07:30numerodixsetnosy: + numerodix
messages: + msg225408
2014-08-15 15:37:13barrysetnosy: + barry
2014-08-15 14:07:15vinay.sajipsetmessages: + msg225351
2014-08-15 05:14:18rhettingersetnosy: + rhettinger
messages: + msg225337
2014-08-15 02:53:33pitrousetmessages: + msg225329
2014-08-14 23:25:54vinay.sajipsetmessages: + msg225324
2014-08-14 16:15:11Saimadhav.Heblikarsetnosy: + Saimadhav.Heblikar
2014-08-14 15:35:03BreamoreBoysetnosy: + BreamoreBoy
2014-08-14 15:18:00pitroucreate