Python作为字典或JSON登录文件

sec*_*sec 8 python logging

我正在尝试设置日志记录,我可以在其中登录stdout并登录到文件。我使用以下代码完成了此任务:

logging.basicConfig(
        level=logging.DEBUG, format='%(asctime)-15s %(levelname)-8s %(message)s',
        datefmt='%a, %d %b %Y %H:%M:%S', handlers=[logging.FileHandler(path), logging.StreamHandler()])
Run Code Online (Sandbox Code Playgroud)

这样的输出如下:

2018-05-02 18:43:33,295 DEBUG    Starting new HTTPS connection (1): google.com
2018-05-02 18:43:33,385 DEBUG    https://google.com:443 "GET / HTTP/1.1" 301 220
2018-05-02 18:43:33,389 DEBUG    Starting new HTTPS connection (1): www.google.com
2018-05-02 18:43:33,490 DEBUG    https://www.google.com:443 "GET / HTTP/1.1" 200 None
Run Code Online (Sandbox Code Playgroud)

我要完成的工作是将此输出记录到文件中,而不是像打印到stdout那样,而是作为类似于此类的字典或JSON对象(同时保持当前的stdout):

[{'time': '2018-05-02 18:43:33,295', 'level': 'DEBUG', 'message': 'Starting new HTTPS connection (1): google.com'}, {...}, {...}]

这可行吗?我知道我可以在过程完成后发布此日志文件,但是我正在寻找一种更优雅的解决方案,因为我正在记录的某些内容本身就是很大的对象。

Bog*_*cea 37

我也处理过这个问题,我个人认为外部库对于这样的事情可能有点过分了。

我研究了一点logging.Formatter背后的代码,并提出了一个子类,在我的例子中它可以解决这个问题(我的目标是拥有一个Filebeat可以读取的JSON文件以进一步登录ElasticSearch)。

班级:

import logging
import json


class JsonFormatter(logging.Formatter):
    """
    Formatter that outputs JSON strings after parsing the LogRecord.

    @param dict fmt_dict: Key: logging format attribute pairs. Defaults to {"message": "message"}.
    @param str time_format: time.strftime() format string. Default: "%Y-%m-%dT%H:%M:%S"
    @param str msec_format: Microsecond formatting. Appended at the end. Default: "%s.%03dZ"
    """
    def __init__(self, fmt_dict: dict = None, time_format: str = "%Y-%m-%dT%H:%M:%S", msec_format: str = "%s.%03dZ"):
        self.fmt_dict = fmt_dict if fmt_dict is not None else {"message": "message"}
        self.default_time_format = time_format
        self.default_msec_format = msec_format
        self.datefmt = None

    def usesTime(self) -> bool:
        """
        Overwritten to look for the attribute in the format dict values instead of the fmt string.
        """
        return "asctime" in self.fmt_dict.values()

    def formatMessage(self, record) -> dict:
        """
        Overwritten to return a dictionary of the relevant LogRecord attributes instead of a string. 
        KeyError is raised if an unknown attribute is provided in the fmt_dict. 
        """
        return {fmt_key: record.__dict__[fmt_val] for fmt_key, fmt_val in self.fmt_dict.items()}

    def format(self, record) -> str:
        """
        Mostly the same as the parent's class method, the difference being that a dict is manipulated and dumped as JSON
        instead of a string.
        """
        record.message = record.getMessage()
        
        if self.usesTime():
            record.asctime = self.formatTime(record, self.datefmt)

        message_dict = self.formatMessage(record)

        if record.exc_info:
            # Cache the traceback text to avoid converting it multiple times
            # (it's constant anyway)
            if not record.exc_text:
                record.exc_text = self.formatException(record.exc_info)

        if record.exc_text:
            message_dict["exc_info"] = record.exc_text

        if record.stack_info:
            message_dict["stack_info"] = self.formatStack(record.stack_info)

        return json.dumps(message_dict, default=str)

Run Code Online (Sandbox Code Playgroud)

用法:

格式化程序必须简单地传递给日志记录处理程序。

    json_handler = FileHandler("foo.json")
    json_formatter = JsonFormatter({"level": "levelname", 
                                    "message": "message", 
                                    "loggerName": "name", 
                                    "processName": "processName",
                                    "processID": "process", 
                                    "threadName": "threadName", 
                                    "threadID": "thread",
                                    "timestamp": "asctime"})
    json_handler.setFormatter(json_formatter)
Run Code Online (Sandbox Code Playgroud)

解释 :

logging.Formatter采用插入的字符串来输出格式化的日志记录,而JsonFormatter采用字典,其中键将是 JSON 字符串中记录值的键,值是与该属性的属性相对应的字符串。可以记录的LogRecord。(此处文档中提供了列表)

主要“问题”是解析日期和时间戳,默认格式化程序实现具有这些类属性default_time_formatdefault_msec_format

default_msec_format 被传递给time.strftime(),并且 default_msec_format 被插值以附加毫秒,因为 time.strftime() 不提供这些格式选项。

原则是,这些现在是实例属性,可以以time_formatmsec_format的形式提供,以自定义父类(未更改,因为它没有被覆盖)formatTime()方法的行为方式。

如果您想自定义时间格式,您可以从技术上覆盖它,但我个人发现使用其他东西要么是多余的,要么限制实际的格式选项。但请随意根据您的需求进行调整。

输出:

由上述格式选项记录的 JSON 记录示例(在类中设置了默认时间格式选项)如下:

{"level": "INFO", "message": "Starting service...", "loggerName": "root", "processName": "MainProcess", "processID": 25103, "threadName": "MainThread", "threadID": 4721200640, "timestamp": "2021-12-04T08:25:07.610Z"}
Run Code Online (Sandbox Code Playgroud)


sec*_*sec 9

所以基于@abarnert,我发现这个链接提供了一个很好的途径来使这个概念在大多数情况下发挥作用。目前的代码是:

logger=logging.getLogger()
logger.setLevel(logging.DEBUG)

file_handler=logging.FileHandler('foo.log')
stream_handler=logging.StreamHandler()

stream_formatter=logging.Formatter(
    '%(asctime)-15s %(levelname)-8s %(message)s')
file_formatter=logging.Formatter(
    "{'time':'%(asctime)s', 'name': '%(name)s', \
    'level': '%(levelname)s', 'message': '%(message)s'}"
)

file_handler.setFormatter(file_formatter)
stream_handler.setFormatter(stream_formatter)

logger.addHandler(file_handler)
logger.addHandler(stream_handler)
Run Code Online (Sandbox Code Playgroud)

虽然它不完全满足要求,但它不需要任何预处理,并且允许我创建两个日志处理程序。

之后,我可以使用类似的东西:

with open('foo.log') as f:
    logs = f.read().splitlines()
for l in logs:
    for key, value in eval(l):
        do something ...
Run Code Online (Sandbox Code Playgroud)

拉取dict对象而不是使用格式不正确的 JSON 来完成我打算完成的任务。

仍然希望有一个更优雅的解决方案。

  • 当心。如果您的消息包含单引号,您的解决方案将会失败。 (9认同)