Issue 22195: Make it easy to replace print() calls with logging calls (original) (raw)

Issue22195

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:06 admin set github: 66391
2019-04-26 20:28:42 BreamoreBoy set nosy: - BreamoreBoy
2014-08-17 23:21:13 vinay.sajip set messages: +
2014-08-17 22:28:32 pitrou set messages: +
2014-08-17 17:53:23 vinay.sajip set messages: +
2014-08-17 14:19:50 numerodix set messages: +
2014-08-16 19:58:14 vinay.sajip set messages: +
2014-08-16 19:07:30 numerodix set nosy: + numerodixmessages: +
2014-08-15 15:37:13 barry set nosy: + barry
2014-08-15 14:07:15 vinay.sajip set messages: +
2014-08-15 05:14:18 rhettinger set nosy: + rhettingermessages: +
2014-08-15 02:53:33 pitrou set messages: +
2014-08-14 23:25:54 vinay.sajip set messages: +
2014-08-14 16:15:11 Saimadhav.Heblikar set nosy: + Saimadhav.Heblikar
2014-08-14 15:35:03 BreamoreBoy set nosy: + BreamoreBoy
2014-08-14 15🔞00 pitrou create