将 sys.stdout 重定向到 python 日志记录

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/975248/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 21:11:24  来源:igfitidea点击:

redirecting sys.stdout to python logging

pythonlogging

提问by UberJumper

So right now we have a lot of python scripts and we are trying to consolidate them and fix and redundancies. One of the things we are trying to do, is to ensure that all sys.stdout/sys.stderr goes into the python logging module.

所以现在我们有很多 python 脚本,我们正在尝试整合它们并修复和冗余。我们正在尝试做的一件事是确保所有 sys.stdout/sys.stderr 都进入 python 日志记录模块。

Now the main thing is, we want the following printed out:

现在主要的是,我们想要打印出以下内容:

[<ERROR LEVEL>] | <TIME> | <WHERE> | <MSG>

Now all sys.stdout / sys.stderr msgs pretty much in all of the python error messages are in the format of [LEVEL] - MSG, which are all written using sys.stdout/sys.stderr. I can parse the fine, in my sys.stdout wrapper and in the sys.stderr wrapper. Then call the corresponding logging level, depending on the parsed input.

现在所有python错误消息中的所有sys.stdout/sys.stderr msgs几乎都是[LEVEL] - MSG的格式,它们都是使用sys.stdout/sys.stderr编写的。我可以在我的 sys.stdout 包装器和 sys.stderr 包装器中解析罚款。然后根据解析的输入调用相应的日志级别。

So basically we have a package called foo, and a subpackage called log. In __init__.pywe define the following:

所以基本上我们有一个名为 foo 的包和一个名为 log 的子包。在__init__.py我们定义以下内容:

def initLogging(default_level = logging.INFO, stdout_wrapper = None, \
                stderr_wrapper = None):
    """
        Initialize the default logging sub system
    """
    root_logger = logging.getLogger('')
    strm_out = logging.StreamHandler(sys.__stdout__)
    strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_TIME_FORMAT, \
                                            DEFAULT_LOG_TIME_FORMAT))
    root_logger.setLevel(default_level)
    root_logger.addHandler(strm_out)

    console_logger = logging.getLogger(LOGGER_CONSOLE)
    strm_out = logging.StreamHandler(sys.__stdout__)
    #strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_MSG_FORMAT, \
    #                                        DEFAULT_LOG_TIME_FORMAT))
    console_logger.setLevel(logging.INFO)
    console_logger.addHandler(strm_out)

    if stdout_wrapper:
        sys.stdout = stdout_wrapper
    if stderr_wrapper:
        sys.stderr = stderr_wrapper


def cleanMsg(msg, is_stderr = False):
    logy = logging.getLogger('MSG')
    msg = msg.rstrip('\n').lstrip('\n')
    p_level = r'^(\s+)?\[(?P<LEVEL>\w+)\](\s+)?(?P<MSG>.*)$'
    m = re.match(p_level, msg)
    if m:
        msg = m.group('MSG')
        if m.group('LEVEL') in ('WARNING'):
            logy.warning(msg)
            return
        elif m.group('LEVEL') in ('ERROR'):
            logy.error(msg)
            return
    if is_stderr:
        logy.error(msg)
    else:
        logy.info(msg)

class StdOutWrapper:
    """
        Call wrapper for stdout
    """
    def write(self, s):
        cleanMsg(s, False)

class StdErrWrapper:
    """
        Call wrapper for stderr
    """
    def write(self, s):
        cleanMsg(s, True)

Now we would call this in one of our scripts for example:

现在我们将在我们的脚本之一中调用它,例如:

import foo.log
foo.log.initLogging(20, foo.log.StdOutWrapper(), foo.log.StdErrWrapper())
sys.stdout.write('[ERROR] Foobar blew')

Which would be converted into an error log message. Like:

这将转换为错误日志消息。喜欢:

[ERROR] | 20090610 083215 | __init__.py | Foobar Blew

Now the problem is when we do that, The module where the error message was logged is now the __init__(corresponding to foo.log.__init__.pyfile) which defeats the whole purpose.

现在的问题是,当我们这样做时,记录错误消息的模块现在是__init__(对应于foo.log.__init__.py文件)这违背了整个目的。

I tried doing a deepCopy/shallowCopy of the stderr/stdout objects, but that does nothing, it still says the module the message occured in __init__.py. How can i make it so this doesn't happen?

我尝试对 stderr/stdout 对象进行 deepCopy/shallowCopy,但这没有任何作用,它仍然表示消息发生在__init__.py. 我怎样才能做到这一点不会发生?

采纳答案by Brian

The problem is that the logging module is looking a single layer up the call stack to find who called it, but now your function is an intermediate layer at that point (Though I'd have expected it to report cleanMsg, not __init__, as that's where you're calling into log()). Instead, you need it to go up two levels, or else pass who your caller is into the logged message. You can do this by inspecting up the stack frame yourself and grabbing the calling function, inserting it into the message.

问题是日志记录模块正在调用堆栈上查找单个层以查找调用它的人,但是现在您的函数此时是一个中间层(尽管我希望它报告cleanMsg,而不是__init__,因为那是您的位置'正在调用 log())。相反,您需要将它提升到两个级别,或者将您的呼叫者是谁传递到记录的消息中。您可以通过自己检查堆栈帧并获取调用函数,将其插入到消息中来完成此操作。

To find your calling frame, you can use the inspect module:

要找到您的调用框架,您可以使用检查模块:

import inspect
f = inspect.currentframe(N)

will look up N frames, and return you the frame pointer. ie your immediate caller is currentframe(1), but you may have to go another frame up if this is the stdout.write method. Once you have the calling frame, you can get the executing code object, and look at the file and function name associated with it. eg:

将查找 N 帧,并返回帧指针。即您的直接调用者是 currentframe(1),但如果这是 stdout.write 方法,您可能需要再上一帧。一旦你有了调用框架,你就可以得到正在执行的代码对象,并查看与之关联的文件和函数名。例如:

code = f.f_code
caller = '%s:%s' % (code.co_filename, code.co_name)

You may also need to put some code to handle non-python code calling into you (ie. C functions or builtins), as these may lack f_code objects.

您可能还需要放置一些代码来处理非 Python 代码调用(即 C 函数或内置函数),因为它们可能缺少 f_code 对象。

Alternatively, following up mikej's answer, you could use the same approach in a custom Logger class inheriting from logging.Logger that overrides findCaller to navigate several frames up, rather than one.

或者,按照mikej 的回答,您可以在继承自 logging.Logger 的自定义 Logger 类中使用相同的方法,该类覆盖 findCaller 以向上导航多个帧,而不是一个。

回答by mikej

I think the problem is that your actual log messages are now being created by the logy.errorand logy.infocalls in cleanMsg, hence that method is the source of the log messages and you are seeing this as __init__.py

我认为问题在于您的实际日志消息现在是由logy.errorlogy.info调用创建的cleanMsg,因此该方法是日志消息的来源,您将其视为__init__.py

If you look in the source of Python's lib/logging/__init__.pyyou will see a method defined called findCallerwhich is what the logging module uses to derive the caller of a logging request.
Perhaps you can override this on your logging object to customise the behaviour?

如果您查看 Python 的源代码,lib/logging/__init__.py您将看到一个定义为调用的方法findCaller,它是日志模块用来派生日志请求调用者的方法。
也许您可以在日志对象上覆盖它以自定义行为?