Django中优雅的Python日志设置

111 投票
4 回答
62323 浏览
提问于 2025-04-15 15:14

我还没找到一个让我满意的方式来在Django中设置Python的日志记录。我的需求其实很简单:

  • 针对不同事件使用不同的日志处理器,也就是说,我想把日志记录到不同的文件里。
  • 在我的模块中能够轻松访问日志记录器。模块应该能很方便地找到它的日志记录器。
  • 应该能很容易地应用到命令行模块上。系统的一部分是独立的命令行或守护进程,日志记录在这些模块中也应该容易使用。

我现在的设置是使用一个logging.conf文件,并在每个我记录日志的模块中设置日志记录。这种方式让我觉得不太对劲。

你有没有喜欢的日志记录设置?请详细说明一下:你是怎么设置配置的(是用logging.conf还是在代码中设置),在哪里/什么时候初始化日志记录器,以及你是怎么在模块中访问它们的等等。

4 个回答

9

我们在最上层的 urls.py 文件中初始化日志记录,使用的是一个 logging.ini 文件。

这个 logging.ini 文件的位置是在 settings.py 文件中指定的,但就这些了。

接下来,每个模块都会这样做:

logger = logging.getLogger(__name__)

为了区分测试、开发和生产环境,我们有不同的 logging.ini 文件。大部分情况下,我们有一个“控制台日志”,它只记录错误信息,并输出到标准错误(stderr)。还有一个“应用日志”,它使用一个常规的滚动日志文件,存放在日志目录下。

130

我知道这个问题已经有解决方案了,但从 Django 1.3 版本开始,有了新的日志设置。

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

当然,建议你查看一下 Django 文档,了解更多内容。

这是一个基本的配置,使用 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 日志配置,它包含以下几个部分:

  • 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'
    },
}

编辑

请查看 请求异常现在总是被记录票据 #16288

我更新了上面的示例配置,明确包含了正确的过滤器,以便在调试模式为真时,默认情况下不会发送电子邮件。

你应该添加一个过滤器:

'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,
    }

否则,如果设置中的 DEBUG 为真,django.core.handlers.base.handle_uncaught_exception 不会将错误传递给 'django.request' 日志记录器。

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

DeprecationWarning: 你在 'mail_admins' 日志处理器上没有定义过滤器:添加隐式的仅调试为假的过滤器

但在 Django 1.4 和 Django 1.5 中,事情仍然会正常工作。

** 编辑结束 **

这个配置受到了 Django 文档中示例配置的强烈启发,但我添加了日志文件的部分。

我通常还会做以下事情:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

然后在我的 Python 代码中,我总是添加一个 NullHandler,以防没有定义任何日志配置。这可以避免没有指定处理器的警告。对于那些不一定只在 Django 中调用的库尤其有用(参考

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.')
58

到目前为止,我发现最好的方法是在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 X.Y.Z”消息(这是一种关于可能配置错误的日志的警告)。

这样做可以满足你所说的需求:

  • 你可以为不同的事件设置不同的日志处理器,就像你现在做的那样。
  • 在你的模块中轻松访问日志记录器 - 使用 getLogger(__name__)
  • 很容易应用于命令行模块 - 它们也会导入 settings.py

更新:注意,从1.3版本开始,Django现在支持 日志记录

撰写回答