【问题标题】:Rotating file handler for JSON logs in Python在 Python 中旋转 JSON 日志的文件处理程序
【发布时间】:2021-04-26 17:04:25
【问题描述】:

我正在使用 python 保存 json 日志。下面是代码:

log_file = 'app_log.json'

log_json = dict()
log_json["Data"] = {}

log_json['Data']['Key1'] = "value1"
log_json['Data']['alert'] = False
log_json['Data']['Key2'] = "N/A"

log_json['Created'] = datetime.datetime.utcnow().isoformat()

with open(log_file, "a") as f:
    json.dump(log_json, f)#, ensure_ascii=False)
    f.write("\n")

现在上面的代码正在生成日志文件。但我注意到文件大小正在增加很多,将来我可能会面临磁盘空间问题。我想知道是否有任何可用于 json 的预构建旋转文件处理程序,我们可以在其中提到固定大小让我们说100mb 并且在达到这个大小时它将删除并重新创建新文件。

我以前曾使用from logging.handlers import RotatingFileHandler 来处理.log 文件,但也想为.json 文件执行此操作。请帮忙。谢谢

【问题讨论】:

标签: python json logging


【解决方案1】:
  1. 您可以使用RotatingFileHandler 实现structured logging
import json
import logging
import logging.handlers
from datetime import datetime

class StructuredMessage:
    def __init__(self, message, /, **kwargs):
        self.message = message
        self.kwargs = kwargs

    def __str__(self):
        return '%s >>> %s' % (self.message, json.dumps(self.kwargs))

_ = StructuredMessage   # optional, to improve readability

log_json = {}
log_json["Data"] = {}

log_json['Data']['Key1'] = "value1"
log_json['Data']['alert'] = False
log_json['Data']['Key2'] = "N/A"

log_json['Created'] = datetime.utcnow().isoformat()

LOG_FILENAME = 'logging_rotatingfile_example.out'

# Set up a specific logger with our desired output level
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# Add the log message handler to the logger
handler = logging.handlers.RotatingFileHandler(
              LOG_FILENAME, maxBytes=20, backupCount=5)
bf = logging.Formatter('%(message)s')
handler.setFormatter(bf)

logger.addHandler(handler)
logger.info(_('INFO', **log_json))

注意:查看here以获取有关structured-logging-python的更多信息

  1. 你也可以使用json-logging-pythonRotatingFileHandler
import logging
import json
import traceback
from datetime import datetime
import copy
import json_logging
import sys

json_logging.ENABLE_JSON_LOGGING = True


def extra(**kw):
    '''Add the required nested props layer'''
    return {'extra': {'props': kw}}


class CustomJSONLog(logging.Formatter):
    """
    Customized logger
    """

    def get_exc_fields(self, record):
        if record.exc_info:
            exc_info = self.format_exception(record.exc_info)
        else:
            exc_info = record.exc_text
        return {'python.exc_info': exc_info}

    @classmethod
    def format_exception(cls, exc_info):
        return ''.join(traceback.format_exception(*exc_info)) if exc_info else ''

    def format(self, record):
        json_log_object = {"@timestamp": datetime.utcnow().isoformat(),
                           "level": record.levelname,
                           "message": record.getMessage(),
                           "caller": record.filename + '::' + record.funcName
                           }
        json_log_object['data'] = {
            "python.logger_name": record.name,
            "python.module": record.module,
            "python.funcName": record.funcName,
            "python.filename": record.filename,
            "python.lineno": record.lineno,
            "python.thread": record.threadName,
            "python.pid": record.process
        }
        if hasattr(record, 'props'):
            json_log_object['data'].update(record.props)

        if record.exc_info or record.exc_text:
            json_log_object['data'].update(self.get_exc_fields(record))

        return json.dumps(json_log_object)


json_logging.init_non_web(custom_formatter=CustomJSONLog, enable_json=True)

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
LOG_FILENAME = 'logging_rotating_json_example.out'

handler = logging.handlers.RotatingFileHandler(
              LOG_FILENAME, maxBytes=20, backupCount=5)

logger.addHandler(handler)

log_json = {}
log_json["Data"] = {}

log_json['Data']['Key1'] = "value1"
log_json['Data']['alert'] = False
log_json['Data']['Key2'] = "N/A"

logger.info('Starting')
logger.debug('Working', extra={"props":log_json})

注意:查看here以获取有关json-logging-python的更多信息

【讨论】:

    【解决方案2】:

    Python 不关心日志文件名。

    您也可以将用于 .log 文件的 the rotating handler 用于 .json 文件。

    参见下面的示例

    # logging_example.py
    
    import logging
    import logging.handlers
    import os
    import time
    
    logfile = os.path.join("/tmp", "demo_logging.json")
    
    logger = logging.getLogger(__name__)
    
    fh = logging.handlers.RotatingFileHandler(logfile, mode='a', maxBytes=1000, backupCount=5)  # noqa:E501
    
    fh.setLevel(logging.DEBUG)
    
    formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
    
    fh.setFormatter(formatter)
    
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    
    while 1:
        time.sleep(1)
        logger.info("Long string to increase the file size")
    

    如果您在 Unix 环境中工作,也可以查看 logrotate。它是一款出色且简单的工具,具有良好的文档,可以完全满足您的需求。

    【讨论】:

    • 我说的是 json 日志记录,这意味着它将有一个 json 文件,该文件将有一个键值对。在您的示例中,我没有看到任何键值对
    • 没关系。您可以在键值对(python dict)中配置您的 json 日志消息,然后使用 json.dumps 将其转换为 yo str 并记录它。 logger.info(json.dumps(YOUR_JSON_MESSAGE_OBJ))
    【解决方案3】:

    您可以在写入/追加到文件之前尝试此操作。这应该检查文件是否已达到最大行大小,然后它将在您照常追加到文件末尾之前从文件开头删除一行代码。

    filename = 'file.txt'
    maxLines = 100
    
    count = len(open(filename).readlines())
    
    if(count > maxLines) {
      with open(filename, 'r') as fin:
        data = fin.read().splitlines(True)
      with open(filename, 'w') as fout:
        fout.writelines(data[1:])
    }
    

    【讨论】:

    • 谁曾否决这个答案,能否评论一下他们投反对票的原因?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-23
    • 1970-01-01
    相关资源
    最近更新 更多