如何在Python中将stdout和stderr重定向到logger

时间:2013-10-17 11:40:38

标签: python logging python-3.x stdout

我有一个RotatingFileHandler的记录器。 我想将所有StdoutStderr重定向到记录器。 怎么做?

8 个答案:

答案 0 :(得分:25)

Not enough rep to comment, but I wanted to add the version of this that worked for me in case others are in a similar situation.

class LoggerWriter:
    def __init__(self, level):
        # self.level is really like using log.debug(message)
        # at least in my case
        self.level = level

    def write(self, message):
        # if statement reduces the amount of newlines that are
        # printed to the logger
        if message != '\n':
            self.level(message)

    def flush(self):
        # create a flush method so things can be flushed when
        # the system wants to. Not sure if simply 'printing'
        # sys.stderr is the correct way to do it, but it seemed
        # to work properly for me.
        self.level(sys.stderr)

and this would look something like:

log = logging.getLogger('foobar')
sys.stdout = LoggerWriter(log.debug)
sys.stderr = LoggerWriter(log.warning)

答案 1 :(得分:12)

如果它是一个全Python系统(即没有C库直接写入fds,正如Ignacio Vazquez-Abrams所询问的那样)那么你可以使用建议的方法here

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

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

然后将sys.stdoutsys.stderr设置为LoggerWriter个实例。

答案 2 :(得分:10)

所有先前的答案似乎都有问题,在不需要的地方添加额外的换行符。最适合我的解决方案来自http://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/,在那里他演示了如何将stdout和stderr发送到记录器:

import logging
import sys

class StreamToLogger(object):
   """
   Fake file-like stream object that redirects writes to a logger instance.
   """
   def __init__(self, logger, log_level=logging.INFO):
      self.logger = logger
      self.log_level = log_level
      self.linebuf = ''

   def write(self, buf):
      for line in buf.rstrip().splitlines():
         self.logger.log(self.log_level, line.rstrip())

logging.basicConfig(
   level=logging.DEBUG,
   format='%(asctime)s:%(levelname)s:%(name)s:%(message)s',
   filename="out.log",
   filemode='a'
)

stdout_logger = logging.getLogger('STDOUT')
sl = StreamToLogger(stdout_logger, logging.INFO)
sys.stdout = sl

stderr_logger = logging.getLogger('STDERR')
sl = StreamToLogger(stderr_logger, logging.ERROR)
sys.stderr = sl

print "Test to standard out"
raise Exception('Test to standard error')

输出如下:

2011-08-14 14:46:20,573:INFO:STDOUT:Test to standard out
2011-08-14 14:46:20,573:ERROR:STDERR:Traceback (most recent call last):
2011-08-14 14:46:20,574:ERROR:STDERR:  File "redirect.py", line 33, in 
2011-08-14 14:46:20,574:ERROR:STDERR:raise Exception('Test to standard error')
2011-08-14 14:46:20,574:ERROR:STDERR:Exception
2011-08-14 14:46:20,574:ERROR:STDERR::
2011-08-14 14:46:20,574:ERROR:STDERR:Test to standard error

请注意,self.linebuf =''是处理flush的地方,而不是实现flush函数。

答案 3 :(得分:5)

您可以使用redirect_stdout上下文管理器:

import logging
from contextlib import redirect_stdout

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logging.write = lambda msg: logging.info(msg) if msg != '\n' else None

with redirect_stdout(logging):
    print('Test')

或者像这样

import logging
from contextlib import redirect_stdout


logger = logging.getLogger('Meow')
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
    fmt='[{name}] {asctime} {levelname}: {message}',
    datefmt='%m/%d/%Y %H:%M:%S',
    style='{'
)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(formatter)
logger.addHandler(ch)

logger.write = lambda msg: logger.info(msg) if msg != '\n' else None

with redirect_stdout(logger):
    print('Test')

答案 4 :(得分:4)

输出重定向正确!

问题

logger.log 和其他函数 (.info/.error/etc.) 将每个调用输出为单独的一行,即隐式添加(格式化和)换行强>到它。

另一方面,

sys.stderr.write 只是将其文字输入写入流,包括部分行。例如:输出“ZeroDivisionError: 除以零”实际上是对 sys.stderr.write 的 4(!) 次单独调用:

sys.stderr.write('ZeroDivisionError')
sys.stderr.write(': ')
sys.stderr.write('division by zero')
sys.stderr.write('\n')

4 种最受好评的方法(1234)因此会产生额外的换行符——只需将“1/0”放入您的程序中您将获得以下信息:

2021-02-17 13:10:40,814 - ERROR - ZeroDivisionError
2021-02-17 13:10:40,814 - ERROR - : 
2021-02-17 13:10:40,814 - ERROR - division by zero

解决方案

中间写入存储在缓冲区中。我使用列表作为缓冲区而不是字符串的原因是为了避免 Shlemiel the painter’s algorithm。 TLDR:它是 O(n) 而不是潜在的 O(n^2)

class LoggerWriter:
    def __init__(self, logfct):
        self.logfct = logfct
        self.buf = []

    def write(self, msg):
        if msg.endswith('\n'):
            self.buf.append(msg.removesuffix('\n'))
            self.logfct(''.join(self.buf))
            self.buf = []
        else:
            self.buf.append(msg)

    def flush(self):
        pass

# To access the original stdout/stderr, use sys.__stdout__/sys.__stderr__
sys.stdout = LoggerWriter(logger.info)
sys.stderr = LoggerWriter(logger.error)
2021-02-17 13:15:22,956 - ERROR - ZeroDivisionError: division by zero

对于 Python 3.9 以下的版本,您可以将 .removesuffix('\n') 替换为不太准确的 .rstrip('\n')

答案 5 :(得分:2)

快速但脆弱的单线

sys.stdout.write = logger.info
sys.stderr.write = logger.error

这样做只是将记录器函数分配给 stdout/stderr .write 调用,这意味着任何写入调用都将调用记录器函数。

这种方法的缺点是对 .write 的调用和记录器函数通常都会添加一个换行符,因此您最终会在日志文件中添加额外的行,这可能是也可能不是问题,具体取决于您的用例。

另一个陷阱是,如果您的记录器写入 stderr 本身,我们会得到无限递归(堆栈溢出错误)。所以只输出到一个文件。

答案 6 :(得分:1)

在Vinay Sajip的回答中添加了同花顺:

class LoggerWriter:
    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

答案 7 :(得分:0)

作为卡梅隆·加农(Cameron Gagnon)回应的发展,我将LoggerWriter类改进为:

class LoggerWriter(object):
    def __init__(self, writer):
        self._writer = writer
        self._msg = ''

    def write(self, message):
        self._msg = self._msg + message
        while '\n' in self._msg:
            pos = self._msg.find('\n')
            self._writer(self._msg[:pos])
            self._msg = self._msg[pos+1:]

    def flush(self):
        if self._msg != '':
            self._writer(self._msg)
            self._msg = ''

现在不受控制的异常看起来更好:

2018-07-31 13:20:37,482 - ERROR - Traceback (most recent call last):
2018-07-31 13:20:37,483 - ERROR -   File "mf32.py", line 317, in <module>
2018-07-31 13:20:37,485 - ERROR -     main()
2018-07-31 13:20:37,486 - ERROR -   File "mf32.py", line 289, in main
2018-07-31 13:20:37,488 - ERROR -     int('')
2018-07-31 13:20:37,489 - ERROR - ValueError: invalid literal for int() with base 10: ''