我有一个带有RotatingFileHandler
的记录器。
我想将所有的Stdout
和Stderr
重定向到记录器。
如何实现?
我有一个带有RotatingFileHandler
的记录器。
我想将所有的Stdout
和Stderr
重定向到记录器。
如何实现?
我没有足够的声望来发表评论,但我希望在这里分享我的解决方法,以便其他人也可以从中受益。
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)
warning archan_pylint:18: <archan_pylint.LoggerWriter object at 0x7fde3cfa2208>
。看起来是stderr对象被打印出来而不是换行符或其他东西,所以我只是删除了flush方法,现在似乎可以工作了。 - pawamoydef flush(self): pass
避免将 <archan_pylint.LoggerWriter object at 0x7fde3cfa2208>
打印到日志中。 - Alon GouldmanPython 3的更新:
linebuf=''
即可)。class StreamToLogger(object):
"""
Fake file-like stream object that redirects writes to a logger instance.
"""
def __init__(self, logger, level):
self.logger = logger
self.level = level
self.linebuf = ''
def write(self, buf):
for line in buf.rstrip().splitlines():
self.logger.log(self.level, line.rstrip())
def flush(self):
pass
然后用类似以下的东西进行测试:
import StreamToLogger
import sys
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(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)
print('Test to standard out')
raise Exception('Test to standard error')
以下是旧版 Python 2.x 的答案和示例输出:
所有之前的答案似乎都存在添加多余换行的问题。对我而言最好的解决方案来自于http://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/,在这里他演示了如何将标准输出和标准错误同时发送到记录器(Logger):
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函数。
如果是一个全Python系统(即没有C库直接写入fds,就像Ignacio Vazquez-Abrams所问的那样),那么您可以尝试使用这里建议的方法:
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.stdout
和sys.stderr
设置为LoggerWriter
实例。
stderr
将其消息逐个单词发送,您知道为什么吗? - orenmaflush()
方法。 - Vinay Sajip您可以使用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')
sys.stdout
/sys.stderr
产生相同的全局副作用呢?@Attila123 - xjcllogger.log
和其他函数(如.info
/.error
/等等)会将每次调用作为单独的一行输出,也就是隐式地添加(格式化和)换行符。
另一方面,sys.stderr.write
只是将文字输入直接写入流中,包括不完整的行。例如:输出 "ZeroDivisionError: division by zero" 实际上是对 sys.stderr.write
进行了 4 次(!) 单独的调用:
sys.stderr.write('ZeroDivisionError')
sys.stderr.write(': ')
sys.stderr.write('division by zero')
sys.stderr.write('\n')
排名前四的最受赞同的方法(1,2,3,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
将中间写入操作存储在缓冲区中。我使用列表作为缓冲区,而不是字符串,是为了避免Shlemiel the painter’s algorithm(夏列米尔画家算法)。简而言之:这样可以将时间复杂度从潜在的O(n^2)降低到O(n)
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以下的版本中,你可以用msg.rstrip('\n')
或者msg[:-1]
代替msg.removesuffix('\n')
。
sys.stderr
的分块调用,会显示为单独的日志。但我想如果您不想要多行日志,也可以使用msg.split('\n')
。 - xjcllogger = logging.getLogger()
对象的.info
函数。相反,你正在传递logging.INFO
,它只是代表日志级别的常量,即数字20
。 - xjcl针对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: ''
sys.stdout.write = logger.info
sys.stderr.write = logger.error
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
flush()
方法是可以的,因为日志处理程序在内部处理刷新:https://dev59.com/22Qn5IYBdhLWcg3wto5W#16634444 - OmerB我的日志记录器引起了无限递归,因为Streamhandler试图写入stdout,而stdout本身是一个日志记录器,从而导致无限递归。
仅为StreamHandler恢复原始的sys.__stdout__
,以便您仍然可以在终端中看到日志显示。
class DefaultStreamHandler(logging.StreamHandler):
def __init__(self, stream=sys.__stdout__):
# Use the original sys.__stdout__ to write to stdout
# for this handler, as sys.stdout will write out to logger.
super().__init__(stream)
class LoggerWriter(io.IOBase):
"""Class to replace the stderr/stdout calls to a logger"""
def __init__(self, logger_name: str, log_level: int):
""":param logger_name: Name to give the logger (e.g. 'stderr')
:param log_level: The log level, e.g. logging.DEBUG / logging.INFO that
the MESSAGES should be logged at.
"""
self.std_logger = logging.getLogger(logger_name)
# Get the "root" logger from by its name (i.e. from a config dict or at the bottom of this file)
# We will use this to create a copy of all its settings, except the name
app_logger = logging.getLogger("myAppsLogger")
[self.std_logger.addHandler(handler) for handler in app_logger.handlers]
self.std_logger.setLevel(app_logger.level) # the minimum lvl msgs will show at
self.level = log_level # the level msgs will be logged at
self.buffer = []
def write(self, msg: str):
"""Stdout/stderr logs one line at a time, rather than 1 message at a time.
Use this function to aggregate multi-line messages into 1 log call."""
msg = msg.decode() if issubclass(type(msg), bytes) else msg
if not msg.endswith("\n"):
return self.buffer.append(msg)
self.buffer.append(msg.rstrip("\n"))
message = "".join(self.buffer)
self.std_logger.log(self.level, message)
self.buffer = []
def replace_stderr_and_stdout_with_logger():
"""Replaces calls to sys.stderr -> logger.info & sys.stdout -> logger.error"""
# To access the original stdout/stderr, use sys.__stdout__/sys.__stderr__
sys.stdout = LoggerWriter("stdout", logging.INFO)
sys.stderr = LoggerWriter("stderr", logging.ERROR)
if __name__ == __main__():
# Load the logger & handlers
logger = logging.getLogger("myAppsLogger")
logger.setLevel(logging.DEBUG)
# HANDLER = logging.StreamHandler()
HANDLER = DefaultStreamHandler() # <--- replace the normal streamhandler with this
logger.addHandler(HANDLER)
logFormatter = logging.Formatter("[%(asctime)s] - %(name)s - %(levelname)s - %(message)s")
HANDLER.setFormatter(logFormatter)
# Run this AFTER you load the logger
replace_stderr_and_stdout_with_logger()
replace_stderr_and_stdout_with_logger()
。class ErrorStreamHandler(log.StreamHandler):
"""Print input log-message into stderr, print only error/warning messages"""
def __init__(self, stream=sys.stderr):
log.Handler.__init__(self, log.WARNING)
self.stream = stream
def emit(self, record):
try:
if record.levelno in (log.INFO, log.DEBUG, log.NOTSET):
return
msg = self.format(record)
stream = self.stream
# issue 35046: merged two stream.writes into one.
stream.write(msg + self.terminator)
self.flush()
except RecursionError: # See issue 36272
raise
except Exception:
self.handleError(record)
class OutStreamHandler(log.StreamHandler):
"""Print input log-message into stdout, print only info/debug messages"""
def __init__(self, loglevel, stream=sys.stdout):
log.Handler.__init__(self, loglevel)
self.stream = stream
def emit(self, record):
try:
if record.levelno not in (log.INFO, log.DEBUG, log.NOTSET):
return
msg = self.format(record)
stream = self.stream
# issue 35046: merged two stream.writes into one.
stream.write(msg + self.terminator)
self.flush()
except RecursionError: # See issue 36272
raise
except Exception:
self.handleError(record)
使用方法:
log.basicConfig(level=settings.get_loglevel(),
format="[%(asctime)s] %(levelname)s: %(message)s",
datefmt='%Y/%m/%d %H:%M:%S', handlers=[ErrorStreamHandler(), OutStreamHandler(settings.get_loglevel())])
stdout
和stderr
消息到我的记录器。 - orenma