我也遇到了这个问题,但个人认为对于这样的问题,使用外部库可能有些过度。
我研究了一下 logging.Formatter 的代码,并编写了一个子类,在我的案例中实现了目标(我的目标是生成一个JSON文件,由Filebeat读取并进一步记录到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)
用法:
只需将格式化程序传递给日志处理程序即可。
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)
说明:
logging.Formatter 接收一个字符串,通过插值输出格式化的日志记录;而 JsonFormatter 接收一个字典,其中键会成为 JSON 字符串中记录值的键,而值则是可以被记录的 LogRecord 的属性对应的字符串。(在文档中提供了可用的列表 这里)。
主要的“问题”是解析日期和时间戳,而默认的格式化程序实现具有这些类属性:default_time_format 和 default_msec_format。
default_msec_format 传递给 time.strftime(),并且 default_msec_format 被插值以添加毫秒,因为 time.strftime() 不提供这些的格式化选项。
原则是这些现在是实例属性,可以以 time_format 和 msec_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"}
Formatter
,将一个LogRecord
进行JSON编码(当然,可能需要先预处理一下)。然后,你需要创建一个标准输出处理器,使用默认的formatter,和一个文件处理器,使用你的自定义formatter。这并不完全容易,但是高级日志教程可以帮助你入手,并且食谱中的一些部分有一些相关的示例代码。 - abarnert