捕获structlog中的所有stdout / stderr以生成JSON日志

时间:2019-03-18 10:49:56

标签: python filebeat python-logging structlog

我目前正在尝试摆脱print()的问题,并开始使用ELK堆栈和structlog模块进行集中日志收集,以生成结构化的json日志行。对于我使用可导入

并与之一起使用的loggingHelper模块编写的模块而言,这非常合适
logger = Logger()

在其他模块和脚本中。这是loggingHelper模块类:

class Logger:
    """
    Wrapper Class to import within other modules and scripts
    All the config and log binding (script
    """
    def __init__(self):
        self.__log = None
        logging.basicConfig(level=logging.DEBUG, format='%(message)s')
        structlog.configure(logger_factory=LoggerFactory(),
                            processors=[structlog.stdlib.add_log_level,
                            structlog.processors.TimeStamper(fmt="iso"),
                            structlog.processors.JSONRenderer()])
        logger = structlog.get_logger()
        main_script = os.path.basename(sys.argv[0]) if sys.argv[0] else None
        frame = inspect.stack()[1]
        log_invocation = os.path.basename(frame[0].f_code.co_filename)
        user = getpass.getuser()

        """
        Who executed the __main__, what was the executed __main__ file, 
        where did the log event happen?
        """
        self.__log = logger.bind(executedScript = main_script,
                                 logBirth = log_invocation,
                                 executingUser = user)

    def info(self, msg, **kwargs):
        self.__log.info(msg, **kwargs)

    def debug(self, msg, **kwargs):
        self.__log.debug(msg, **kwargs)

    def error(self, msg, **kwargs):
        self.__log.error(msg, **kwargs)

    def warn(self, msg, **kwargs):
        self.__log.warning(msg, **kwargs)

这会产生格式正确的输出(每行一个JSON),filebeat可以读取并转发到Elasticsearch。 但是,第三方发布者会完全破坏格式正确的日志。

{"executingUser": "xyz", "logBirth": "efood.py", "executedScript": "logAlot.py", "context": "SELECT displayname FROM point_of_sale WHERE name = '123'", "level": "debug", "timestamp": "2019-03-15T12:52:42.792398Z", "message": "querying local"}
{"executingUser": "xyz", "logBirth": "efood.py", "executedScript": "logAlot.py", "level": "debug", "timestamp": "2019-03-15T12:52:42.807922Z", "message": "query successful: got 0 rows"}
building service object
auth version used is: v4
Traceback (most recent call last):
  File "logAlot.py", line 26, in <module>
    ef.EfoodDataControllerMerchantCenter().get_displayname(123)
  File "/home/xyz/src/toolkit/commons/connectors/efood.py", line 1126, in get_displayname
    return efc.select_from_local(q)['displayname'].values[0]
IndexError: index 0 is out of bounds for axis 0 with size 0

如您所见,来自第三方库(googleapiclient)的信息级和错误级消息都是经过打印的,而无需通过日志记录处理器。

使用我编写的loggingHelper模块,捕获和格式化在执行一个脚本时发生的所有事情的最佳方法(也是大多数pythonic)是什么?这甚至是最佳做法吗?

编辑:当前,记录器确实确实在向stdout写入数据,然后使用>>和2>&1将其重定向到crontab中的文件。如果我想重定向通过第三方库日志记录写入stdout / stderr的所有内容,这对我来说似乎是一种不好的做法,因为这会导致循环,对吗?因此,我的目标不是重定向,而是捕获日志记录处理器中的所有内容。相应地更改了标题。

此外,这是我要实现的目标的粗略概述。我很乐意接受与此不同的一般批评和建议。 enter image description here

2 个答案:

答案 0 :(得分:1)

配置logging模块

  

您已经知道,structlog需要配置   python中已经存在的日志记录功能。

     

http://www.structlog.org/en/stable/standard-library.html

logging.basicConfig在此处支持streamfilename的选项

https://docs.python.org/3/library/logging.html#logging.basicConfig

可以,您可以指定记录器将创建句柄并将其所有输出定向的文件名。根据您的设置方式,这可能是您通常重定向到的文件

import logging

logging.basicConfig(level=logging.DEBUG, format='%(message)s', filename='output.txt')

或者,您可以将StringIO对象传递给构建器,稍后可以从中读取并重定向到所需的输出目标

import logging
import io

stream = io.StringIO()

logging.basicConfig(level=logging.DEBUG, format='%(message)s', stream=stream)
  

有关StringIO的更多信息,请点击此处

     

https://docs.python.org/3/library/io.html#io.TextIOBase

@bruno 在他的回答中指出,请勿在{{1​​}}中执行此操作,因为您可能最终会在同一过程中多次调用这段代码。

答案 1 :(得分:0)

第一件事:您不应在类初始值设定项中进行任何记录器配置(logging.basicConfiglogging.dictConfig等)-记录配置应该仅进行一次,并且只能进行一次在流程启动时。 logging模块的重点是完全分离日志记录调用

第二点:我不是structlog专家(这是一种轻描淡写的说法,实际上这是我第一次听说此软件包),但是您得到的结果是您的代码片段所期望的:只有您自己的代码使用structlog,所有其他库(stdlib或第3部分)仍将使用stdlib记录器并发出纯文本日志。

根据我在structlog文档中看到的内容,似乎为wrap the stdlib's loggers using the structlog.stdlib.LoggerFactoryadd specific formatters to have a more consistant output提供了一些方法。我尚未对此进行测试(但尚未正式发布),官方文档有点稀疏且缺少可用的实际示例(至少我找不到任何示例),但是this article似乎有一个更明确的示例(以适应您的情况)自己的上下文和用例)。

CAVEAT :正如我所说的,我从未使用过structlog(我第一次听说这个库),所以我可能误解了一些东西,当然,您必须尝试寻找了解如何正确配置整个组件以使其按预期工作。

作为旁注:在类unix的系统中,stdout应该用于程序的输出(我的意思是“期望的输出” =>程序的实际结果),而所有错误/报告/调试消息属于stderr。除非您有令人信服的理由,否则应尝试遵守该约定(至少对于命令行工具而言,以便可以将它们以unix方式链接/管道化)。