Django 在格式化程序中记录自定义属性

Posted

技术标签:

【中文标题】Django 在格式化程序中记录自定义属性【英文标题】:Django logging custom attributes in formatter 【发布时间】:2017-11-09 11:30:12 【问题描述】:

Django 如何使用日志记录来使用格式化程序中的自定义属性进行日志记录?例如,我正在考虑记录登录的用户名。

settings.py脚本中,定义了LOGGING变量:

LOGGING = 
    'version': 1,
    'disable_existing_loggers': False,
    'filters': 
        'require_debug_false': 
            '()': 'django.utils.log.RequireDebugFalse'
        ,
    ,
    'formatters' : 
        'info_format' : 
            'format' : '%(asctime)s %(levelname)s - %(message)s',
        ,
    

我希望使用一种格式,例如:

'format' : '%(asctime).19s %(levelname)s - %(username)s: %(message)s'

其中 username 将是当前登录的用户。也许可以在此处添加任何其他类型的会话变量。

这里的一种解决方法是在记录器方法上使用extra 参数,它接收带有键的字典作为我想在格式字符串上使用的字符串:

logger.info(message, extra='username' : request.user.username)

另一个(丑陋的)解决方法是构建消息属性以包含不属于日志格式化程序的默认属性的内容。

message = request.user.username + " - " + message
logger.info(message)

但是,有没有办法设置具有某些属性的格式字符串并让 Django 自动将它们提供给日志记录 API?例如,如果 %(username)s,request.user.username,可能是其他任何人...

【问题讨论】:

请显示一些已经完成的代码。 @JohnPeters 你去! 【参考方案1】:

您可以使用过滤器来添加您的自定义属性。例如:

def add_my_custom_attribute(record):
    record.myAttribute = 'myValue'
    record.username = record.request.user.username 
    return True

LOGGING = 
    'version': 1,
    'disable_existing_loggers': False,
    'filters': 
        ...
        'add_my_custom_attribute': 
            '()': 'django.utils.log.CallbackFilter',
            'callback': add_my_custom_attribute,
        
    ,
    'handlers': 
        ...
        'django.server': 
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'filters': ['add_my_custom_attribute'],
            'formatter': 'django.server',
        ,            
    ,
    ...


通过安装过滤器,您可以处理每条日志记录并决定是否应将其从记录器传递到处理程序。

过滤器获取日志记录,其中包含日志的所有详细信息(即:时间、严重性、请求、状态码)。

格式化程序使用记录的属性将其格式化为字符串消息。如果您将自定义属性添加到该记录 - 它们也将可供格式化程序使用。

【讨论】:

您能否详细说明如何使用我通过日志格式化字符串上的过滤器添加的某些自定义属性? 详细介绍了过滤器。 需要注意的一点是,日志记录也可能发生在没有request(例如通用python代码或models.py)的地方。所以生产版本可能需要更多的代码来保证健壮性。 (作为一个例子,当然没问题!我从中学到了一些东西,我会多看看过滤器!) Python docs供参考 看起来它不再起作用了 - AttributeError: 'LogRecord' object has no attribute 'request'【参考方案2】:

extra 关键字不是解决方法。这是编写自定义格式化程序最有说服力的方式,除非您完全编写 custom logging。

format: '%(asctime).19s %(levelname)s - %(username)s: %(message)s'
logging.basicConfig(format=format)
logger.info(message, extra='username' : request.user.username)

文档中的一些注释 (**kwars for Django logger):

额外传入的字典中的键不应与日志系统使用的键发生冲突。

如果格式化程序期望的字符串丢失,则不会记录该消息。

此功能旨在用于特殊情况,但并非总是如此。

【讨论】:

使用extra 是可行的,但如果您一直想要/想要添加它,它会变得乏味。最初的问题是一种仅在一个地方指定它的标准方法(即日志记录设置)。【参考方案3】:

我将为这个问题提供许多可能的完整答案之一:

Django 如何使用日志记录来使用格式化程序中的自定义属性进行日志记录?例如,我正在考虑记录登录的用户名。

其他答案涉及通过 python 的日志记录实用程序添加额外上下文信息的方式。使用过滤器将附加信息附加到日志记录的方法是理想的,并且最好在文档中进行描述:

https://docs.python.org/3/howto/logging-cookbook.html#using-filters-to-impart-contextual-information

这仍然没有告诉我们如何以通用的方式从请求中获取用户。以下库执行此操作:

https://github.com/ninemoreminutes/django-crum

因此,将两者结合起来,您将对所提出的问题有一个完整的答案。

import logging
from crum import get_current_user

class ContextFilter(logging.Filter):
    """
    This is a filter injects the Django user
    """

    def filter(self, record):

        record.user = get_current_user()
        return True

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG,
                        format='User: %(user)-8s %(message)s')
    a1 = logging.getLogger('a.b.c')

    f = ContextFilter()
    a1.addFilter(f)
    a1.debug('A debug message')

这需要在正确安装 CRUM 库的 Django 请求-响应周期内发生。

【讨论】:

以上是关于Django 在格式化程序中记录自定义属性的主要内容,如果未能解决你的问题,请参考以下文章

自定义 Django 模型字段中的“对象没有属性”

关于HTML5如何自定义属性

django 1.3 用于在开发环境中进行调试的日志记录

Django中模型

如何在 django 表单中设置自定义 HTML 属性?

Django - 自定义模型保存方法不显示属性值