Django 中 Python 日志记录的优雅设置

Posted

技术标签:

【中文标题】Django 中 Python 日志记录的优雅设置【英文标题】:Elegant setup of Python logging in Django 【发布时间】:2010-12-08 14:26:45 【问题描述】:

我还没有找到让我满意的使用 Django 设置 Python 日志记录的方法。我的要求很简单:

针对不同事件的不同日志处理程序 - 也就是说,我希望能够记录到不同的文件 轻松访问我的模块中的记录器。该模块应该能够轻松找到它的记录器。 应该很容易应用于命令行模块。系统的一部分是独立的命令行或守护进程。这些模块应该可以轻松使用日志记录。

我当前的设置是使用logging.conf 文件并在我登录的每个模块中设置日志记录。感觉不对。

您有喜欢的日志记录设置吗?请详细说明:您如何设置配置(您是使用logging.conf 还是在代码中设置),您在何处/何时启动记录器,以及如何在您的模块中访问它们等。

【问题讨论】:

您可能会发现以下截屏视频很有用 - ericholscher.com/blog/2008/aug/29/…。此外,Simon Willison 提出了对登录 Django 的更好支持(参见 simonwillison.net/2009/Sep/28/ponies)。 @Dominic Rodger - 你已经可以在 Django 中灵活地记录应用程序,Simon 的提议主要是为了便于登录 Django 内部。在 Python 中正在进行的工作是将基于字典的配置添加到 Python 日志记录中,Django 可能会从中受益。 【参考方案1】:

我目前正在使用我自己创建的日志记录系统。它使用 CSV 格式进行日志记录。

django-csvlog

这个项目还没有完整的文档,但我正在努力。

【讨论】:

答案中提到的链接已损坏 链接失效,请更新【参考方案2】:

到目前为止,我发现的最好方法是在 settings.py 中初始化日志记录设置——别无他处。您可以使用配置文件,也可以通过编程方式一步一步进行 - 这取决于您的要求。关键是我通常将我想要的处理程序添加到根记录器,使用级别,有时使用 logging.Filters 来获取我想要的事件到适当的文件、控制台、系统日志等。你当然可以将处理程序添加到任何其他记录器也一样,但根据我的经验,通常不需要这样做。

在每个模块中,我定义了一个记录器使用

logger = logging.getLogger(__name__)

并使用它来记录模块中的事件(如果我想进一步区分)使用一个记录器,它是上面创建的记录器的子记录器。

如果我的应用程序可能用于未在 settings.py 中配置登录的站点,我会在某处定义一个 NullHandler,如下所示:

#someutils.py

class NullHandler(logging.Handler):
    def emit(self, record):
        pass

null_handler = NullHandler()

并确保将它的一个实例添加到我的应用程序中使用日志记录的模块中创建的所有记录器中。 (注意:NullHandler 已经在 Python 3.1 的日志包中,并且将在 Python 2.7 中。)所以:

logger = logging.getLogger(__name__)
logger.addHandler(someutils.null_handler)

这样做是为了确保您的模块在未在 settings.py 中配置登录的站点中正常运行,并且您不会收到任何烦人的“No handlers could be found for logger XYZ”消息(即有关可能错误配置的日志记录的警告)。

这样做符合您的要求:

您可以像当前一样为不同的事件设置不同的日志处理程序。 轻松访问模块中的记录器 - 使用 getLogger(__name__)。 很容易适用于命令行模块 - 它们还导入 settings.py

更新:请注意,从 1.3 版开始,Django 现在合并了support for logging。

【讨论】:

这不要求每个模块都在配置中定义一个处理程序(你不能使用 foo 的处理程序来处理 foo.bar)?请参阅我们多年前在groups.google.com/group/comp.lang.python/browse_thread/thread/… 的对话 @andrew cooke:您可以使用foo 的处理程序来处理记录到foo.bar 的事件。回覆。该线程 - fileConfig 和 dictConfig 现在都具有防止禁用旧记录器的选项。请参阅此问题:bugs.python.org/issue3136,这是在您的问题bugs.python.org/issue2697 之后的几个月内出现的 - 无论如何,它自 2008 年 6 月以来就已经解决了。 someutils.getLoggerlogging.getLogger返回记录器并添加一个null_handler不是更好吗? @7yl4r 您不需要 每个 记录器都添加了 NullHandler - 通常只是您的包层次结构的***记录器。所以这将是矫枉过正,IMO。【参考方案3】:

我们使用logging.ini 文件在***urls.py 中初始化日志记录。

logging.ini 的位置在settings.py 中提供,仅此而已。

然后每个模块都会这样做

logger = logging.getLogger(__name__)

为了区分测试、开发和生产实例,我们有不同的 logging.ini 文件。在大多数情况下,我们有一个“控制台日志”,它只带有错误信息。我们有一个“应用程序日志”,它使用一个常规的滚动日志文件,该文件进入日志目录。

【讨论】:

我最终使用了这个,除了在 settings.py 而不是 urls.py 中初始化 如何在 logging.ini 文件中使用 settings.py 中的设置?例如,我需要 BASE_DIR 设置,所以我可以告诉它在哪里存储我的日志文件。 @slypete:我们不使用 logging.ini 中的设置。由于日志记录在很大程度上是独立的,因此我们不使用任何 Django 设置。是的,有可能重复某些事情。不,这并没有太大的实际区别。 在这种情况下,我会在每次安装我的应用程序时创建一个单独的 logging.ini 文件。 @slypete:每个安装都有一个 settings.py。对于每个安装,您还有一个 logging.ini。另外,您可能还为每个安装提供了一个 Apache conf 文件。加上一个wsgi接口文件。我不确定你的意思是什么。【参考方案4】:

我知道这已经是一个已解决的答案,但是根据 django >= 1.3 有一个新的日志记录设置。

从旧到新不是自动的,所以我想我会在这里写下来。

当然还有更多信息,请查看the django doc。

这是基本配置,默认使用 django-admin createproject v1.3 创建 - 里程可能会随着最新的 django 版本而改变:

LOGGING = 
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': 
        'mail_admins': 
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        
    ,
    'loggers': 
        'django.request': 
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        
    

此结构基于标准Python logging dictConfig,它规定了以下块:

formatters - 对应的值将是一个字典,其中每个键是一个格式化程序 ID,每个值是一个描述如何配置相应格式化程序实例的字典。 filters - 对应的值将是一个字典,其中每个键是一个过滤器 ID,每个值是一个描述如何配置相应过滤器实例的字典。

handlers - 对应的值将是一个字典,其中每个键是一个处理程序 ID,每个值是一个描述如何配置相应处理程序实例的字典。每个处理程序都有以下键:

class(必填)。这是处理程序类的完全限定名称。 level(可选)。处理程序的级别。 formatter(可选)。此处理程序的格式化程序的 ID。 filters(可选)。此处理程序的过滤器 ID 列表。

我通常至少会这样做:

添加 .log 文件 配置我的应用程序以写入此日志

翻译成:

LOGGING = 
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': 
        'verbose': 
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        ,
        'simple': 
            'format': '%(levelname)s %(message)s'
        ,
    ,
    'filters': 
        'require_debug_false': 
            '()': 'django.utils.log.RequireDebugFalse'
        
    ,
    'handlers': 
        'null': 
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        ,
        'console':
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        ,
        # I always add this handler to facilitate separating loggings
        'log_file':
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(VAR_ROOT, 'logs/django.log'),
            'maxBytes': '16777216', # 16megabytes
            'formatter': 'verbose'
        ,
        'mail_admins': 
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        
    ,
    'loggers': 
        'django.request': 
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        ,
        'apps':  # I keep all my of apps under 'apps' folder, but you can also add them one by one, and this depends on how your virtualenv/paths are set
            'handlers': ['log_file'],
            'level': 'INFO',
            'propagate': True,
        ,
    ,
    # you can also shortcut 'loggers' and just configure logging for EVERYTHING at once
    'root': 
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO'
    ,

编辑

见request exceptions are now always logged和Ticket #16288:

我更新了上面的示例 conf 以明确包含正确的 mail_admins 过滤器,因此默认情况下,当 debug 为 True 时不会发送电子邮件。

你应该添加一个过滤器:

'filters': 
    'require_debug_false': 
        '()': 'django.utils.log.RequireDebugFalse'
    
,

并将其应用于 mail_admins 处理程序:

    'mail_admins': 
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    

否则,如果 settings.DEBUG 为 True,django.core.handers.base.handle_uncaught_exception 不会将错误传递给“django.request”记录器。

如果你不在 Django 1.5 中这样做,你会得到一个

弃用警告:您没有在“mail_admins”日志记录处理程序上定义过滤器:添加隐式 debug-false-only 过滤器

但在 django 1.4 和 django 1.5 中,事情仍然可以正常工作。

** 结束编辑 **

该 conf 受到 django 文档中的示例 conf 的强烈启发,但添加了日志文件部分。

我也经常做以下事情:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

然后在我的 python 代码中,我总是添加一个 NullHandler 以防万一没有定义任何日志记录配置。这避免了未指定处理程序的警告。对于不一定只在 Django 中调用的库特别有用 (ref)

import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
class NullHandler(logging.Handler): #exists in python 3.1
    def emit(self, record):
        pass
nullhandler = logger.addHandler(NullHandler())

# here you can also add some local logger should you want: to stdout with streamhandler, or to a local file...

[...]

logger.warning('etc.etc.')

希望这会有所帮助!

【讨论】:

Stefano,非常感谢您的详细回答,非常有帮助。这可能值得升级到 1.3。 Parand,这绝对是(恕我直言!)值得升级到 django 1.3,尽管有几点需要注意才能顺利过渡 - 如果遇到麻烦,请打开一个新的 SO 问题; -) 顺便说一句:我仍然使用这种设置和文件日志,但我转移到sentry进行生产! @clime 好吧,我试图在答案本身中解释它:以防万一没有定义任何日志记录配置。这避免了未指定处理程序的警告。对于不一定只在 Django 中调用的库特别有用(参考) 我不明白你是如何使用这个定义的:'null': 'level':'DEBUG', 'class':'django.utils.log.NullHandler',

以上是关于Django 中 Python 日志记录的优雅设置的主要内容,如果未能解决你的问题,请参考以下文章

python中更优雅的记录日志

python Django日志记录设置

python Django日志记录设置,已通过1.11版测试

『Python』优雅的记录日志——loguru

Python3 - Loguru 相见恨晚的输出日志工具

Python3 - Loguru 相见恨晚的输出日志工具