如何在Python中将stdout和stderr重定向到记录器

如何在Python中将stdout和stderr重定向到记录器,python,logging,python-3.x,stdout,Python,Logging,Python 3.x,Stdout,我有一个记录器,它有一个旋转文件处理程序。 我想将所有Stdout和Stderr重定向到记录器。 如何做到这一点?如果它是一个全Python系统(如Ignacio Vazquez Abrams所问,没有直接向fds写入的C库),那么您可能可以使用建议的方法: 然后将sys.stdout和sys.stderr设置为LoggerWriter实例。没有足够的代表发表评论,但我想添加一个对我有用的版本,以防其他人处于类似情况 class LoggerWriter: def __init__(se

我有一个记录器,它有一个
旋转文件处理程序
。 我想将所有
Stdout
Stderr
重定向到记录器。
如何做到这一点?

如果它是一个全Python系统(如Ignacio Vazquez Abrams所问,没有直接向fds写入的C库),那么您可能可以使用建议的方法:


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

没有足够的代表发表评论,但我想添加一个对我有用的版本,以防其他人处于类似情况

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)
这看起来像:

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

Vinay Sajip的回答中添加了flush:

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
Python 3的更新:

  • 包括一个伪flush函数,该函数可以防止在预期函数的位置出现错误(Python2只需
    linebuf=''
  • 请注意,如果从解释器会话记录输出(和日志级别),与从文件运行输出(和日志级别)不同。从文件运行会产生预期的行为(以及下面的输出)
  • 我们仍然消除了其他解决方案没有的额外换行
类StreamToLogger(对象):
"""
伪文件类流对象,将写入重定向到记录器实例。
"""
定义初始化(自身、记录器、级别):
self.logger=记录器
self.level=level
self.linebuf=“”
def写入(自身,buf):
对于buf.rstrip().splitlines()中的行:
self.logger.log(self.level,line.rstrip())
def冲洗(自):
通过
然后用以下方法进行测试:

log = logging.getLogger('foobar')
sys.stdout = LoggerWriter(log.debug)
sys.stderr = LoggerWriter(log.warning)
导入StreamToLogger
导入系统
导入日志记录
logging.basicConfig(
级别=logging.DEBUG,
格式='(asctime)s:%(levelname)s:%(name)s:%(message)s',
filename='out.log',
filemode='a'
)
log=logging.getLogger('foobar')
sys.stdout=StreamToLogger(log,logging.INFO)
sys.stderr=StreamToLogger(log,logging.ERROR)
打印(“测试到标准输出”)
引发异常(“测试到标准错误”)
请参阅下面的旧Python 2.x答案和示例输出:

所有先前的答案似乎都有问题,在不需要的地方添加额外的换行符。最适合我的解决方案是从中,他演示了如何将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=''
是处理刷新的地方,而不是实现刷新功能。

您可以使用重定向\u 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')

作为对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: ''
2018-07-3113:20:37482-错误-回溯(最近一次呼叫最后一次):
2018-07-31 13:20:37483-错误-文件“mf32.py”,第317行,在
2018-07-31 13:20:37485-错误-主要()
2018-07-31 13:20:37486-错误-文件“mf32.py”,第289行,主
2018-07-31 13:20:37488-错误-整数(“”)
2018-07-31 13:20:37489-错误-值错误:以10为基数的int()的文本无效:“”
快速但易碎的单层衬里 这只是将记录器函数分配给stdout/stderr
.write
调用,这意味着任何write调用都将调用记录器函数

这种方法的缺点是,对
.write
的调用和记录器函数通常都会添加一个换行符,这样您的日志文件中就会有额外的行,这可能是一个问题,也可能不是,这取决于您的使用情况

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

输出重定向正确! 问题
logger.log
和其他函数(
.info
/
.error
/etc)将每个调用作为单独的一行输出,即隐式地向其添加(格式化和)新行

另一方面,
sys.stderr.write
只将其文本输入写入流,包括部分行。例如:输出“ZeroDivisionError:DivisionbyZero”实际上是对
sys.stderr.write
的4个独立调用:

sys.stderr.write('ZeroDivisionError')
sys.stderr.write(': ')
sys.stderr.write('division by zero')
sys.stderr.write('\n')
因此,4种投票最多的方法(,)会产生额外的换行符——只需在程序中输入“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
解决方案 将中间写入存储在缓冲区中。我使用列表而不是字符串作为缓冲区的原因是为了避免错误。TLDR:它是O(n)而不是潜在的O(n^2)


对于低于Python 3.9的版本,您可以将
.removesuffix('\n')
替换为精度较低的
.rstrip('\n')

您有直接写入FDs 1和2的外部模块/库吗?@IgnacioVazquez Abrams我不太明白您的意思,但我会尝试解释。我正在使用几个python进程,我想从它们中重定向所有
stdout
stderr
消息到我的记录器。可能是重复的谢谢,这完成了任务,但出于某种原因
stderr
将消息分别发送到每个单词,你知道为什么吗?@orenma可能是因为write是逐字调用的。您可以调整我的示例代码以更紧密地满足您的需要。如果在重定向stderr后调用sys.stderr.flush(),该怎么办?我无法使库代码不使用sys.stderr.flush()等。处理其所有属性的最佳方法是什么?如果涉及到C库怎么办?然后呢?如何让C库输出到同一个LoggerWriter?此代码已获得许可。我不确定它是否可以发布在SO上,这需要与兼容。知道我为什么会收到此错误消息吗?“在中忽略了异常:AttributeError'StreamToLogger'对象没有属性'flush'”删除代码段的最后两行,错误消息就会消失……扩展TextIOBase“更安全”。某物
sys.stderr.write('ZeroDivisionError')
sys.stderr.write(': ')
sys.stderr.write('division by zero')
sys.stderr.write('\n')
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
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