Python에서 stdout 및 stderr을 로거로 리디렉션하는 방법
나는 로거를 가지고 있습니다.RotatingFileHandler▁all다니▁want▁redirect▁iStdout그리고.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)
Python 3용 업데이트:
- 기능이 예상되는 곳에서 오류를 방지하는 더미 플러시 기능을 포함합니다(파이썬 2는 그냥 괜찮았습니다).
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/, 에서 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 = ''플러시 기능을 구현하는 대신 플러시가 처리되는 곳입니다.
이그나시오 바스케스-아브람스가 질문한 것처럼 모든 파이썬 시스템(즉, 직접 fds를 쓰는 C 라이브러리가 없음)이라면 다음과 같은 방법을 사용할 수 있습니다.
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.stderrLoggerWriter예를 들면
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')
출력 리디렉션이 올바르게 수행되었습니다!
문제
logger.log 함수기타기능(기능().info/.error/etc.)는 각 호출을 별도의 행으로 출력합니다. 즉, 암시적으로 새 행을 추가(포맷하고)합니다.
sys.stderr.write반면에 부분 라인을 포함하여 문자 그대로의 입력을 스트림에 씁니다.예:출력 "ZeroDivisionError: division by zero"는 실제로 다음에 대한 4(!)의 개별 호출입니다.sys.stderr.write:
sys.stderr.write('ZeroDivisionError')
sys.stderr.write(': ')
sys.stderr.write('division by zero')
sys.stderr.write('\n')
따라서 가장 많이 업데이트된 4가지 접근 방식(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
해결책
중간 쓰기를 버퍼에 저장합니다.제가 문자열이 아닌 목록을 버퍼로 사용하는 이유는 화가의 슐레밀 알고리즘을 피하기 위해서입니다.TLDR: 잠재적으로 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.removesuffix('\n')둘 중 하나로msg.rstrip('\n')또는msg[:-1].
카메론 가뇽의 반응에 대한 진화로서, 저는 개선했습니다.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
이렇게 하면 단순히 로거 기능을 stdout/stderr에 할당할 수 있습니다..write모든 쓰기 호출이 로거 함수를 호출한다는 것을 의미합니다.
이 접근 방식의 단점은 두 통화 모두 다음과 같은 것입니다..write그리고 로거 기능은 일반적으로 새 줄을 추가하므로 로그 파일에 줄이 추가됩니다. 이는 사용 사례에 따라 문제가 될 수도 있고 문제가 되지 않을 수도 있습니다.
또 다른 함정은 로거가 stderr 자체에 쓰기만 하면 무한 재귀(스택 오버플로 오류)가 발생한다는 것입니다.파일 출력만 가능합니다.
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
StreamHandler로 인해 무한 반복이 발생하는 문제 해결
내 로거는 무한 재귀를 일으키고 있었습니다. 스트림 핸들러가 stdout에 글을 쓰려고 했기 때문입니다. 그 자체가 무한 재귀로 이어지는 로거 ->입니다.
해결책
원본 복원sys.__stdout__터미널에 표시되는 로그를 계속 볼 수 있도록 StreamHandler 전용입니다.
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()로거를 초기화한 후(코드의 마지막 비트)
정보 및 오류 메시지를 별도의 스트림(info to stdout, errors to stderr)으로 기록하려면 다음과 같은 방법을 사용할 수 있습니다.
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())])
언급URL : https://stackoverflow.com/questions/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python
'programing' 카테고리의 다른 글
| Gitstash에서 추가된 파일을 강제로 덮어씁니다. (0) | 2023.05.04 |
|---|---|
| Terraform은 모듈에 따라 달라집니다. (0) | 2023.05.04 |
| Lru_cache(functools에서)는 어떻게 작동합니까? (0) | 2023.05.04 |
| 중단점은 현재 적중되지 않습니다.Silverlight 응용 프로그램에서 이 문서에 대한 기호가 로드되지 않았습니다. (0) | 2023.05.04 |
| 윈도우 개발 기계를 사용하여 아이폰용으로 개발하려면 어떻게 해야 합니까? (0) | 2023.04.29 |