简体   繁体   中英

How can INFO and DEBUG logging message be sent to stdout and higher level message to stderr

Is there an easy way with python's logging module to send messages with a DEBUG or INFO level and the one with a higher level to different streams?

Is it a good idea anyway?

import logging
import sys

class LessThanFilter(logging.Filter):
    def __init__(self, exclusive_maximum, name=""):
        super(LessThanFilter, self).__init__(name)
        self.max_level = exclusive_maximum

    def filter(self, record):
        #non-zero return means we log this message
        return 1 if record.levelno < self.max_level else 0

#Get the root logger
logger = logging.getLogger()
#Have to set the root logger level, it defaults to logging.WARNING
logger.setLevel(logging.NOTSET)

logging_handler_out = logging.StreamHandler(sys.stdout)
logging_handler_out.setLevel(logging.DEBUG)
logging_handler_out.addFilter(LessThanFilter(logging.WARNING))
logger.addHandler(logging_handler_out)

logging_handler_err = logging.StreamHandler(sys.stderr)
logging_handler_err.setLevel(logging.WARNING)
logger.addHandler(logging_handler_err)

#demonstrate the logging levels
logger.debug('DEBUG')
logger.info('INFO')
logger.warning('WARNING')
logger.error('ERROR')
logger.critical('CRITICAL')

Implementation aside, I do think it is a good idea to use the logging facilities in python to output to the terminal, in particular because you can add another handler to additionally log to a file. If you set stdout to be INFO instead of DEBUG, you can even include additional DEBUG information that the user wouldn't standardly see in the log file.

I had the same problem and wrote a custom logging handler called SplitStreamHandler:

import sys
import logging

class SplitStreamHandler(logging.Handler):
    def __init__(self):
        logging.Handler.__init__(self)

    def emit(self, record):
        # mostly copy-paste from logging.StreamHandler
        try:
            msg = self.format(record)
            if record.levelno < logging.WARNING:
                stream = sys.stdout
            else:
                stream = sys.stderr
            fs = "%s\n"

            try:
                if (isinstance(msg, unicode) and
                    getattr(stream, 'encoding', None)):
                    ufs = fs.decode(stream.encoding)
                    try:
                        stream.write(ufs % msg)
                    except UnicodeEncodeError:
                        stream.write((ufs % msg).encode(stream.encoding))
                else:
                    stream.write(fs % msg)
            except UnicodeError:
                stream.write(fs % msg.encode("UTF-8"))

            stream.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

Just for your convenience adding everything together with the formatter in one package:

# shared formatter, but you can use separate ones:
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(threadName)s - %(message)s'
formatter = logging.Formatter(FORMAT)

# single app logger:
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)

# 2 handlers for the same logger:
h1 = logging.StreamHandler(sys.stdout)
h1.setLevel(logging.DEBUG)
# filter out everything that is above INFO level (WARN, ERROR, ...)
h1.addFilter(lambda record: record.levelno <= logging.INFO)
h1.setFormatter(formatter)
log.addHandler(h1)

h2 = logging.StreamHandler(sys.stderr)
# take only warnings and error logs
h2.setLevel(logging.WARNING)
h2.setFormatter(formatter)
log.addHandler(h2)

# profit:
log.info(...)
log.debug(...)

My use case was to redirect stdout to a datafile while seeing errors on the screen during processing.

right from the updated docs, it cover this case pretty well now.

http://docs.python.org/howto/logging.html#logging-advanced-tutorial

import sys # Add this.
import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler( sys.__stdout__ ) # Add this
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

i've mentioned on comments the two changes required from the example to make the output go to stdout. you may also use filters to redirect depending on the level.

more information to understand the changes is at http://docs.python.org/library/logging.handlers.html#module-logging.handlers

Here's the compact solution I use (tested with Python 3.10):

import logging
import sys

root_logger = logging.getLogger()

# configure default StreamHandler to stderr
logging.basicConfig(
    format="%(asctime)s | %(levelname)-8s | %(filename)s:%(funcName)s(%(lineno)d) | %(message)s",
    level=logging.INFO,  # overall log level; DEBUG or INFO make most sense
)
stderr_handler = root_logger.handlers[0]  # get default handler
stderr_handler.setLevel(logging.WARNING)  # only print WARNING+

# add another StreamHandler to stdout which only emits below WARNING
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.addFilter(lambda rec: rec.levelno < logging.WARNING)
stdout_handler.setFormatter(stderr_handler.formatter)  # reuse the stderr formatter
root_logger.addHandler(stdout_handler)

You will have to associated a handler to each stream you want logged statements to be sent to. Python has 5 logging levels - CRITICAL , ERROR , WARNING , INFO , and DEBUG .

Level Numeric Value Stream
CRITICAL 50 STDERROR
ERROR 40 STDERROR
WARNING 30 STDOUT
INFO 20 STDOUT
DEBUG 10 STDOUT
NOTSET 0

Let's assume you want that Critical and Error logs should go to STDERROR , while others should go to STDOUT - as shown in the table above.

Here are the steps you should follow.

Step1 Initialize the logging module

import logging, sys
log = logging.getLogger()

Step 2 Create two streams, one attached to STDERROR, other to STDOUT

hStErr = logging.StreamHandler(sys.stderr)
hStOut = logging.StreamHandler(sys.stdout)

Step 3 Set log level for each handler. This tells handler to only log statements which have log level equal to or higher than the level we set. eg. if we set this to WARNING , the handler will only handle logging statements for WARNING , ERROR , and CRITICAL levels. Also make sure that the log level is set in handler only, and not with the log object, so that there is no conflict

hStErr.setLevel('ERROR')
hStOut.setLevel('DEBUG')
log.setLevel('NOTSET')

Now here comes a catch. While hStErr will only output logging for ERROR and CRITICAL , hStOut will output for all 5 levels. Remember that setLevel only tells the minimum logging level which should be handled, so all levels which are greater will also be handled. To limit hStOut to not handle ERROR and CRITICAL , we use a filter.

Step 4 Specify a filter so that ERROR , and CRITICAL aren't handled by hStOut

hStOut.addFilter(lambda x : x.levelno < logging.ERROR)

Step 5 Add these handlers to logger

log.addHandler(hStErr)
log.addHandler(hStOut)

Here are all the pieces together.

import logging, sys
log = logging.getLogger()
hStErr = logging.StreamHandler(sys.stderr)
hStOut = logging.StreamHandler(sys.stdout)

hStErr.setLevel('ERROR')
hStOut.setLevel('DEBUG')
log.setLevel('NOTSET')

hStOut.addFilter(lambda x : x.levelno < logging.ERROR)

log.addHandler(hStErr)
log.addHandler(hStOut)

log.error("error log")
log.info("info log")

Output when we run this script.

error log
info log

Pycharm IDE colors output from std error red. The following image shows that the error log statement above was sent to stderr .

在 pycharm IDE 控制台中输出

If we comment the addFilter line in above script, we will see the following output.

error log
error log
info log

在 pycharm IDE 控制台中输出

Note that without filter hStOut will output logging statements from both INFO and ERROR, while for INFO hStErr outputs nothing, and hStOut outputs a single statement - info log

不一定是个好主意(看到信息和调试消息与正常输出混合可能会令人困惑!),但可行,因为您可以为每个处理程序对象和一个自定义过滤器,以便挑选和选择哪个日志记录每个处理程序得到处理。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import sys

class LessThenFilter(logging.Filter):
    def __init__(self, level):
        self._level = level
        logging.Filter.__init__(self)

    def filter(self, rec):
        return rec.levelno < self._level

log = logging.getLogger()
log.setLevel(logging.NOTSET)

sh_out = logging.StreamHandler(stream=sys.stdout)
sh_out.setLevel(logging.DEBUG)
sh_out.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
sh_out.addFilter(LessThenFilter(logging.WARNING))
log.addHandler(sh_out)

sh_err = logging.StreamHandler(stream=sys.stderr)
sh_err.setLevel(logging.WARNING)
sh_err.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
log.addHandler(sh_err)

logging.critical('x')
logging.error('x')
logging.warning('x')
logging.info('x')
logging.debug('x')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM