在Django中优雅地设置Python日志记录


101

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

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

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

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


1
您可能会发现以下截屏有用-ericholscher.com/blog/2008/aug/29/…。另外,Simon Willison提出了对Django登录更好的支持(请参见simonwillison.net/2009/Sep/28/ponies)。
Dominic Rodger

@Dominic Rodger-您已经可以在Django中对应用程序进行灵活的日志记录,Simon的建议主要是为了便于在Django内部构件中进行日志记录。Python正在进行着将基于字典的配置添加到Python日志中的工作,Django可能会从中受益。
Vinay Sajip,2009年

Answers:


57

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

在每个模块中,我使用

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中的日志记录的站点中正常运行,并且不会收到任何令人讨厌的“找不到记录器XYZ处理程序”的消息(这是有关潜在警告的警告)错误配置的日志记录)。

通过这种方式可以满足您规定的要求:

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

更新:请注意,从1.3版开始,Django现在合并了对logging的支持


这是否不要求每个模块都在配置中定义一个处理程序(您不能将处理程序用于foo来处理foo.bar)?见我们不得不年前在谈话groups.google.com/group/comp.lang.python/browse_thread/thread/...
安德鲁·库克

1
@andrew cooke:您可以使用处理程序foo来处理记录到的事件foo.bar。回覆。该线程-fileConfig和dictConfig现在都具有防止禁用旧记录器的选项。请参见以下问题:bugs.python.org/issue3136,它出现在您的问题bugs.python.org/issue2697之后的几个月中 -无论如何,它已于2008
。– Vinay Sajip,2010年

logger = someutils.getLogger(__name__)在已添加null_handler的情况下someutils.getLogger从返回记录器的地方做会更好logging.getLogger吗?
7yl4r

1
@ 7yl4r您不需要每个记录器都有NullHandler添加的东西-通常只是包层次结构的顶级记录器。IMO,这太过分了。
Vinay Sajip '16

122

我知道这已经是一个解决的答案,但是按照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日志dictConfig,该命令规定了以下块:

  • formatters -相应的值将是一个dict,其中每个键是一个格式化程序ID,每个值是一个描述如何配置相应的Formatter实例的dict。
  • filters -相应的值将是一个dict,其中每个键是一个过滤器ID,每个值是一个描述如何配置相应的Filter实例的dict。
  • handlers-相应的值将是一个dict,其中每个键是一个处理程序ID,每个值是一个描述如何配置相应的Handler实例的dict。每个处理程序具有以下键:

    • 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

我更新了上面的示例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,
    }

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

如果您在Django 1.5中不执行此操作,则会得到一个

DeprecationWarning:您在'mail_admins'日志记录处理程序上未定义过滤器:添加隐式的debug-false-only过滤器

但是在django 1.4和django 1.5中,一切仍然可以正常工作。

**结束编辑**

该conf受到django doc中示例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 2011年

Parand,绝对值得(IMHO!)升级到django 1.3,尽管要注意平滑过渡的几点要点-如果遇到麻烦,请提出一个新的SO问题;-)
Stefano,

顺便说一句:我仍然使用这种设置和文件日志,但是我转到哨兵进行制作!
Stefano

@clime好,我尝试在答案本身中进行解释:以防万一没有定义任何日志conf。这样可以避免未指定任何处理程序的警告。对于不一定只在Django中调用的库特别有用(ref)
Stefano

我看不出你如何使用这样的定义:“空”:{“等级”:“DEBUG”,“类”:“django.utils.log.NullHandler”,}
风土

9

我们urls.py使用logging.ini文件初始化顶级日志记录。

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

然后每个模块都执行

logger = logging.getLogger(__name__)

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


我最终使用了它,除了在settings.py而不是urls.py中初始化
Parand

如何使用logging.ini文件中的settings.py中的设置?例如,我需要BASE_DIR设置,因此我可以告诉它将日志文件存储在何处。
slypete 2010年

@slypete:我们不使用logging.ini中的设置。由于日志记录在很大程度上是独立的,因此我们不使用任何Django设置。是的,有可能重复某些操作。不,这没有多大实际意义。
S.Lott

在这种情况下,我将在每次安装应用程序时使用单独的logging.ini文件。
slypete 2010年

@slypete:每个安装都有一个settings.py。对于每个安装,您还都有一个logging.ini。另外,您可能还为每个安装都有一个Apache conf文件。加上一个wsgi接口文件。我不确定你的意思是。
S.Lott

6

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

django-csvlog

该项目仍然没有完整的文档,但是我正在研究它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.