我目前正试图摆脱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
正如您所看到的,来自第三方librara(googleapiclient)的信息级别和错误级别消息都是打印而不通过日志记录处理器。
使用我编写的loggingHelper模块捕获和格式化一个脚本执行过程中发生的所有事情的最佳方式(以及大多数pythonic)是什么?这是最好的做法吗?
编辑:当前记录器确实写入stdout本身,然后使用>>和2>&1重定向到crontab中的文件。如果我想通过第三方库日志记录重定向写入stdout / stderr的所有内容,这对我来说似乎是不好的做法,因为这会导致循环,对吗?因此,我的目标不是重定向,而是捕获我的日志记录处理器中的所有内容。相应地更改了标题。
logging
module正如您已经想到的那样,
structlog
需要配置python中已存在的日志记录功能。
logging.basicConfig
支持stream
和filename
的选项
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的更多信息,请参阅此处
正如@bruno在他的回答中指出的那样,不要在__init__
中这样做,因为你最终可能会在同一个过程中多次调用这段代码。
首先要做的是:你不应该在你的类初始化程序中执行任何记录器配置(logging.basicConfig
,logging.dictConfig
等) - 日志记录配置应该只在进程启动时执行一次。 logging
模块的重点是完全解耦日志记录调用
第二点:我不是structlog
专家(这是一个轻描淡写 - 这实际上是我第一次听到这个包)但你得到的结果是你的代码片段所期望的:只有你自己的代码使用structlog
,所有其他库(stdlib或第三部分)仍将使用stdlib
记录器并发出纯文本日志。
从我在structlog
doc中看到的,它似乎为wrap the stdlib's loggers using the structlog.stdlib.LoggerFactory
和add specific formatters to have a more consistant output提供了一些方法。我还没有测试过这个(还),官方文档有点稀疏,缺乏可用的实际例子(至少我找不到)但this article似乎有一个更明确的例子(适应你自己的背景和使用当然是一个案例)。
CAVEAT:正如我所说,我从未使用过structlog
(我第一次听说这个lib)所以我可能误解了一些东西,你当然必须尝试找出如何正确配置整个事情以使其工作预期。
作为旁注:在类似unix的系统中,stdout
应该用于程序的输出(我的意思是“预期输出”=>程序的实际结果),而所有错误/报告/调试消息都属于stderr
。除非你有令人信服的理由否则你应该尝试坚持这个约定(至少对于命令行工具,所以你可以用unix方式链接/管道它们)。