I can’t figure out how to log info-level messages to stdout, but everything else to stderr. I already read this http://docs.python.org/library/logging.html. Any suggestion?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
The following script, log1.py:
import logging, sys
class SingleLevelFilter(logging.Filter):
def __init__(self, passlevel, reject):
self.passlevel = passlevel
self.reject = reject
def filter(self, record):
if self.reject:
return (record.levelno != self.passlevel)
else:
return (record.levelno == self.passlevel)
h1 = logging.StreamHandler(sys.stdout)
f1 = SingleLevelFilter(logging.INFO, False)
h1.addFilter(f1)
rootLogger = logging.getLogger()
rootLogger.addHandler(h1)
h2 = logging.StreamHandler(sys.stderr)
f2 = SingleLevelFilter(logging.INFO, True)
h2.addFilter(f2)
rootLogger.addHandler(h2)
logger = logging.getLogger("my.logger")
logger.setLevel(logging.DEBUG)
logger.debug("A DEBUG message")
logger.info("An INFO message")
logger.warning("A WARNING message")
logger.error("An ERROR message")
logger.critical("A CRITICAL message")
when run, produces the following results.
C:temp>log1.py A DEBUG message An INFO message A WARNING message An ERROR message A CRITICAL message
As you’d expect, since on a terminal sys.stdout and sys.stderr are the same. Now, let’s redirect stdout to a file, tmp:
C:temp>log1.py >tmp A DEBUG message A WARNING message An ERROR message A CRITICAL message
So the INFO message has not been printed to the terminal – but the messages directed to sys.stderr have been printed. Let’s look at what’s in tmp:
C:temp>type tmp An INFO message
So that approach appears to do what you want.
Method 2
Generally, I think it makes sense to redirect messages lower than WARNING to stdout, instead of only INFO messages.
Based on Vinay Sajip‘s excellent answer, I came up with this:
class MaxLevelFilter(Filter):
'''Filters (lets through) all messages with level < LEVEL'''
def __init__(self, level):
self.level = level
def filter(self, record):
return record.levelno < self.level # "<" instead of "<=": since logger.setLevel is inclusive, this should be exclusive
MIN_LEVEL= DEBUG
#...
stdout_hdlr = StreamHandler(sys.stdout)
stderr_hdlr = StreamHandler(sys.stderr)
lower_than_warning= MaxLevelFilter(WARNING)
stdout_hdlr.addFilter( lower_than_warning ) #messages lower than WARNING go to stdout
stdout_hdlr.setLevel( MIN_LEVEL )
stderr_hdlr.setLevel( max(MIN_LEVEL, WARNING) ) #messages >= WARNING ( and >= STDOUT_LOG_LEVEL ) go to stderr
#...
Method 3
Since my edit was rejected, here’s my answer. @goncalopp’s answer is good but doesn’t stand alone or work out of the box. Here’s my improved version:
import sys, logging
class LogFilter(logging.Filter):
"""Filters (lets through) all messages with level < LEVEL"""
# http://stackoverflow.com/a/24956305/408556
def __init__(self, level):
self.level = level
def filter(self, record):
# "<" instead of "<=": since logger.setLevel is inclusive, this should
# be exclusive
return record.levelno < self.level
MIN_LEVEL = logging.DEBUG
stdout_hdlr = logging.StreamHandler(sys.stdout)
stderr_hdlr = logging.StreamHandler(sys.stderr)
log_filter = LogFilter(logging.WARNING)
stdout_hdlr.addFilter(log_filter)
stdout_hdlr.setLevel(MIN_LEVEL)
stderr_hdlr.setLevel(max(MIN_LEVEL, logging.WARNING))
# messages lower than WARNING go to stdout
# messages >= WARNING (and >= STDOUT_LOG_LEVEL) go to stderr
rootLogger = logging.getLogger()
rootLogger.addHandler(stdout_hdlr)
rootLogger.addHandler(stderr_hdlr)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Example Usage
>>> logger.debug("A DEBUG message")
>>> logger.info("An INFO message")
>>> logger.warning("A WARNING message")
>>> logger.error("An ERROR message")
>>> logger.critical("A CRITICAL message")
Method 4
Simplest handler to send colored output to stderr:
class ColorStderr(logging.StreamHandler):
def __init__(self):
class AddColor(logging.Formatter):
def format(self, record: logging.LogRecord):
msg = super().format(record)
# Green/Cyan/Yellow/Red/Redder based on log level:
color = '33[1;' + ('32m', '36m', '33m', '31m', '41m')[
min(4,int(4 * record.levelno / logging.FATAL))]
return color + record.levelname + '33[1;0m: ' + msg
super().__init__(sys.stderr)
self.setFormatter(AddColor())
Use with:
logging.basicConfig(level=logging.INFO, handlers=[ColorStderr()])
Or you can even apply the formatter directly to the current log handler, with no need for ColorStderr:
logging.getLogger().handlers[0].setFormatter(AddColor())
Method 5
Here is my answer (combination of the others):
logger = logging.getLogger(__name__)
class LogFilter(logging.Filter):
def __init__(self, level):
self.level = level
def filter(self, record):
return record.levelno < self.level
info_handler = logging.StreamHandler(stdout)
error_handler = logging.StreamHandler(stderr)
info_handler.addFilter(LogFilter(logging.WARNING))
error_handler.setLevel(max(logging.DEBUG, logging.WARNING))
logger.handlers = [info_handler, error_handler]
logger.setLevel(logging.DEBUG)
Method 6
Try This Monkey Patch ~~
import sys
import logging
import threading
def _logging_handle(self, record):
self.STREAM_LOCKER = getattr(self, "STREAM_LOCKER", threading.RLock())
if self.stream in (sys.stdout, sys.stderr) and record.levelname in self.FIX_LEVELS:
try:
self.STREAM_LOCKER.acquire()
self.stream = sys.stdout
self.old_handle(record)
self.stream = sys.stderr
finally:
self.STREAM_LOCKER.release()
else:
self.old_handle(record)
def patch_logging_stream(*levels):
"""
writing some logging level message to sys.stdout
example:
patch_logging_stream(logging.INFO, logging.DEBUG)
logging.getLogger('root').setLevel(logging.DEBUG)
logging.getLogger('root').debug('test stdout')
logging.getLogger('root').error('test stderr')
"""
stream_handler = logging.StreamHandler
levels = levels or [logging.DEBUG, logging.INFO]
stream_handler.FIX_LEVELS = [logging.getLevelName(i) for i in levels]
if hasattr(stream_handler, "old_handle"):
stream_handler.handle = stream_handler.old_handle
stream_handler.old_handle = stream_handler.handle
stream_handler.handle = _logging_handle
Test
#
patch_logging_stream(logging.INFO, logging.DEBUG)
logging.getLogger('root').setLevel(logging.DEBUG)
logging.getLogger('root').debug('test root stdout')
logging.getLogger('root').error('test root stderr')
Test Output
$ python3 test_patch_logging.py 2>/dev/null
DEBUG:root:test root stdout
$ python3 test_patch_logging.py 1>/dev/null
ERROR:root:test root stderr
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0