【问题标题】:Strip pformat() newlines (\n) from dict in jsonlogger.JsonFormatter从 jsonlogger.JsonFormatter 中的 dict 中去除 pformat() 换行符 (\n)
【发布时间】:2022-01-29 14:01:56
【问题描述】:

我正在使用 json jsonlogger.JsonFormatter 编写 json 日志文件,如下所示(只是提取了代码的重要部分)。对于控制台,我使用普通的StreamHandler

在程序执行期间,我在字典中收集一些统计信息,在程序完成时记录这些统计信息。我使用pformat 来获得更易于阅读的输出,这在控制台streamHandler 上运行良好。

from pprint import pformat
logger.info(pformat(stats_dict, indent=2, sort_dicts=False))

带有 pformat 的 dict 的控制台输出:

2022-01-29 13:53:43 [INFO   ] extractor    
{ 'search_count': 240,
  'no_response': 0,
  'no_result': 8}

但这也会导致 json 格式化程序 无法正确识别字典的键/值元组,而是整个字典将在 message 键中显示为单个字符串值,包括\n 来自 pformat:

{"asctime": "2022-01-29 13:17:41,430", "levelname": "INFO", "levelno": 20, "module": "extractor", "message": "{ 'search_count': 240,\n  'no_response': 0,\n  'no_result': 8,\n }", "lineno": 463}

如果我使用pformat,那么 json 文件处理程序将正确识别所有键/值元组并将它们记录下来。

{"asctime": "2022-01-29 13:53:43,487", "levelname": "INFO", "levelno": 20, "module": "extractor", "message": null, "lineno": 464, "search_count": 240, "no_response": 0, "no_result": 8}

这是设置我的处理程序的部分:

from pythonjsonlogger import jsonlogger

def get_json_handler(file_base, path, level):
    path = os.path.join(path, '')
    logfilename = file_base + "_" + level.lower() + ".json"
    json_handler = logging.handlers.TimedRotatingFileHandler(f"{path}{logfilename}", when="midnight", interval=1, backupCount=3)
    format = jsonlogger.JsonFormatter("%(asctime)s %(levelname)s %(levelno)s %(module)s %(message)s %(lineno)s ")
    json_handler.setLevel(level)
    json_handler.setFormatter(format)
    return json_handler

def get_stream_handler(level):
    stream_handler = logging.StreamHandler()
    format = logging.Formatter("%(asctime)s [%(levelname)-7s] %(module)-12s %(message)s", "%Y-%m-%d %H:%M:%S")
    stream_handler.setLevel(level)
    stream_handler.setFormatter(format)
    return stream_handler

有没有一种简单的方法可以去除 json 格式化程序 本身中 pformat 放置的 \n 换行符?

【问题讨论】:

    标签: python logging


    【解决方案1】:

    不要预先格式化字典。您现在传递的是字符串而不是 dict,这在 JSON 日志输出中很明显。

    改为向您的控制台记录器添加一个自定义格式化程序,它可以检测您何时传递字典并将 pformat 应用于它们。例如。

    import logging
    import pprint
    from copy import copy
    
    class PFormatter(logging.Formatter):
        def __init__(self, *args, pformat_args=None, **kwargs):
            super().__init__(*args, **kwargs)
            self.pformat_args = pformat_args or {}
    
        def format(self, record: logging.LogRecord):
            if isinstance(record.msg, dict):
                new_record = copy(record)
                new_record.msg = pprint.pformat(record.msg, **self.pformat_args)
                return super().format(new_record)
            else:
                return super().format(record)
    

    要选择性地控制何时应用 pformat,您可以使用extra。例如:

    import logging
    import pprint
    from copy import copy
    
    
    class PFormatter(logging.Formatter):
        def __init__(self, *args, pformat_args=None, **kwargs):
            super().__init__(*args, **kwargs)
            self.pformat_args = pformat_args or {}
    
        def format(self, record: logging.LogRecord):
            # NB: extra is passed a dictionary, but its keys become
            # attributes of the log record
            pformat = getattr(record, 'pformat', False)
            if pformat:
                if isinstance(pformat, dict):
                    # Allow temporary overriding of default args if 
                    # pformat is a dict
                    pformat_args = dict(self.pformat_args, **pformat)
                else:
                    pformat_args = self.pformat_args
                new_record = copy(record)
                new_record.msg = pprint.pformat(record.msg, **pformat_args)
                return super().format(new_record)
            else:
                return super().format(record)
    

    你可能会这样使用:

    # setup logger
    formatter = PFormatter(
        '%(asctime)s %(name)s: %(msg)s',
        pformat_args={'indent': 2, 'width': 2},
    )
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    log = logging.getLogger(__name__)
    log.addHandler(handler)
    log.setLevel('DEBUG')
    
    # actual logging
    some_dict = {
        'search_count': 240,
        'no_response': 0,
        'no_result': 8,
    }
    
    PFORMAT = {'pformat': True}
     # just a normal log call, no pformat
    log.info(some_dict)
    # now pretty print
    log.info(some_dict, extra=PFORMAT)
    # change indent for one call
    log.info(some_dict, extra={'pformat': {'indent': 8}})
    # back to default indent
    log.info(some_dict, extra=PFORMAT)
    # apply pformat to anything, not just dicts!
    log.info(list(range(20)), extra=PFORMAT)
    

    【讨论】:

    • 谢谢沙丘,它就像一个魅力!我像这样扩展了 dict-check:if isinstance(record.msg, dict) and record.msg.get('pformat') == 1: 以便我可以控制我想打印 pformatted 的 dicts,因为我有更短的 dicts,我仍然希望在一行上拥有。
    • 很高兴它有帮助。我最初有类似的东西,但认为答案太过分了。所以我只关注如何使用格式化程序来延迟应用格式化。我使用了附加功能,因此元信息不需要成为输出的一部分。我会弹出一个例子。
    • 您是否也有任何想法,如何使电子邮件处理程序使用换行符?我可以在电子邮件(包含 Json/Dict)中看到 /n 如果我也在 JsonFormatter 上使用相同的方法,但电子邮件客户端不会将其转换为换行符。
    • 我对你的解决方案也有疑问,在创建此类的实例时如何在def __init__(self, *args, pformat_args=None, **kwargs): 中使用pformat_args?我试过format = PFormatter("%(asctime)s %(levelname)-7s | %(module)-12s | %(message)s", "%Y-%m-%d %H:%M:%S", indent=2),但它给了我一个错误....
    • 后一个问题我想通了:format = PFormatter("%(asctime)s %(levelname)-7s | %(module)-12s | %(message)s", "%Y-%m-%d %H:%M:%S", pformat_args={'indent':2, 'sort_dicts':False})
    【解决方案2】:

    我没有测试它,因为我没有你的完整程序和记录器等。但是根据文档,你可以覆盖 JsonFormatter 的加载功能。因此,如您所知,可以删除有问题的字符并转换回字典(不确定是否需要这样做或保留字符串)。

    class CustomJsonFormatter(jsonlogger.JsonFormatter):
        def add_fields(self, log_record, record, message_dict):
            super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
            if log_record.get('message'):
                # remove '\n'
                message_str = log_record['message'].replace("\n", '')
                # remove last comma
                message_str = "".join(message_str.rsplit(",", 1))
                # transform to dict (required?)
                log_record['message'] = json.loads(message_str)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-11
      • 2010-11-30
      • 1970-01-01
      • 2015-04-24
      • 1970-01-01
      • 2012-09-13
      • 1970-01-01
      • 2020-02-16
      相关资源
      最近更新 更多