Django 中的 Python 日志记录

Posted

技术标签:

【中文标题】Django 中的 Python 日志记录【英文标题】:Python logging in Django 【发布时间】:2010-09-25 10:33:41 【问题描述】:

我正在开发一个 Django 应用程序,并且我正在尝试使用 Python 的日志记录模块进行错误/跟踪记录。理想情况下,我希望为站点的不同区域配置不同的记录器。到目前为止,我已经完成了所有这些工作,但有一件事让我摸不着头脑。

我将根记录器转到 sys.stderr,并且我已经配置了另一个记录器来写入文件。这是在我的 settings.py 文件中:

sviewlog = logging.getLogger('MyApp.views.scans')
view_log_handler = logging.FileHandler('C:\\MyApp\\logs\\scan_log.log')
view_log_handler.setLevel(logging.INFO)
view_log_handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s'))
sviewlog.addHandler(view_log_handler)

看起来很简单。但是,问题出在:我写到 sviewlog 的任何内容都会被写入日志文件两次。根记录器只打印一次。就像 addHandler() 被调用了两次。当我将代码通过调试器时,这正是我所看到的。 settings.py 中的代码被执行了两次,因此创建了两个 FileHandler 并将其添加到同一个记录器实例中。但为什么?我该如何解决这个问题?

谁能告诉我这里发生了什么?我尝试将 sviewlog 记录器/处理程序实例化代码移动到使用它的文件中(因为这对我来说实际上似乎是合适的位置),但我在那里遇到了同样的问题。我在网上看到的大多数示例只使用根记录器,我更喜欢拥有多个记录器。

【问题讨论】:

【参考方案1】:

为什么使用 python logger 而不是 django-logging?试试看,它可能只是解决你的问题。

http://code.google.com/p/django-logging/wiki/Overview

目前它只允许查看根记录器,但您可以确保写入多个记录器。

【讨论】:

据我了解,django-logging 与记录到单独的文件没有任何关系。它的主要用途是在您的应用页面上输出日志消息,这不是我目前需要的功能。所以我想不出它为什么能解决我的问题。 django-logging 不再维护。项目页面上有建议使用github.com/robhudson/django-debug-toolbar 进行客户端日志记录【参考方案2】:

很难评论您的具体案例。如果 settings.py 执行了两次,那么每发送一个日志就会得到两行是正常的。

我们遇到了同样的问题,因此我们在项目中设置了一个专门用于日志记录的模块。该模块具有“模块单例”模式,因此我们只执行一次有趣的代码。

看起来像这样:

def init_logging():
    stdoutHandler = logging.StreamHandler( sys.stdout )
    stdoutHandler.setLevel( DEBUG )
    stdoutHandler.setFormatter( logging.Formatter( LOG_FORMAT_WITH_TIME ) )
    logging.getLogger( LOG_AREA1 ).addHandler( stdoutHandler )

logInitDone=False #global variable controlling the singleton.
if not logInitDone:
    logInitDone = True
    init_logging()

第一次导入 log.py 会正确配置日志记录。

【讨论】:

我是对的 - 这样你必须import log, logging 而不是像logger = logging.getLogger(LOG_AREA1) 那样写smthng 然后才像logger.info(bla-bla-bla) 那样使用它?从 init_logging() (return logging.getLogger...) 返回记录器并将其设置为模块 var 是否更好?说logger = init_logging()。这样你就可以导入 log.py 并开始使用它import log; log.logger.info(bla-bla-bla)? 哦!当然,如果您愿意,您可以使用模块级记录器:import log; module_logger = log.logger.getLogger(__name__) 这整个logInitDone 的东西都没用。看它,当执行两次时,logInitDone 总是设置回False,所以init_logging() 仍然会被调用两次。这仅适用于模块在导入时只执行一次,如this answer 中所述。 @RickyA,您在 Python 编程的一般情况下是正确的,但在所问问题的情况下是错误的。使用 django 时,我不知道为什么,但是 settings.py 在同一个程序空间中执行了两次。因此需要用一个全局守卫来保护它。 这可以在Django的模块化结构中遵循。如何在 Django 的通用视图中使用它?【参考方案3】:

一种骇人听闻的方式,但您可以尝试将日志记录代码放在 admin.py 中。它应该只导入一次。

或者;您可以先检查MyApp.views.scans 日志是否存在?如果它存在(可能引发错误),您可以简单地跳过创建(因此不再添加处理程序)。一种更清洁的方法,但我还没有尝试过。

还必须有一个更合适的位置来放置此代码 (__init__.py?)。 settings.py 用于设置。

【讨论】:

【参考方案4】:

请允许我回答我自己的问题。这里的根本问题是 settings.py 被导入两次,甚至更多(参见here)。 (我仍然不明白为什么会这样。也许一些 Django 专家可以向我解释。)其他一些模块似乎也是如此。在这一点上,我认为假设 settings.py 将被导入多少次是不明智的。就此而言,这样的假设通常是不安全的。我在settings.py以外的地方有过这段代码,结果也差不多。

您必须围绕此编写代码。也就是说,您必须在向其添加其他处理程序之前检查您的记录器是否存在现有处理程序。这有点难看,因为将多个处理程序(即使是同一类型)附加到一个记录器是完全合理的。有一些解决方案可以解决这个问题。一种是检查 logger 对象的 handlers 属性。如果您只想要一个处理程序并且您的长度 > 0,则不要添加它。就我个人而言,我不喜欢这个解决方案,因为它会因为更多的处理程序而变得混乱。

我更喜欢这样的东西(感谢 Thomas Guettler):

# file logconfig.py
if not hasattr(logging, "set_up_done"):
    logging.set_up_done=False

def set_up(myhome):
    if logging.set_up_done:
        return
    # set up your logging here
    # ...
    logging.set_up_done=True

我必须说,我希望 Django 多次导入 settings.py 的事实得到更好的记录。而且我会想象我的配置以某种方式导致了这种多次导入,但是我无法找出导致问题的原因和原因。也许我只是在他们的文档中找不到这一点,但我认为这是你需要警告用户的事情。

【讨论】:

我不是 Django 专家,但我想 settings.py 会在每次加载一个模块时导入,其中包含一个带有“设置”的导入语句。使用 settings.py 的模块越多,导入的次数就越多。 @HartleyBrody python caches modules 因为它们被加载,所以通常一个给定的模块只执行一次,无论它出现在多少个导入语句中。 根据Django 1.4 release notes,这个问题可能已经解决了。【参考方案5】:

您可以通过在进行初始化时检查处理程序的数量来解决您的问题。

def init_logging():
    stdoutHandler = logging.StreamHandler( sys.stdout )
    stdoutHandler.setLevel( DEBUG )
    stdoutHandler.setFormatter( logging.Formatter( LOG_FORMAT_WITH_TIME ) )
    logger = logging.getLogger( LOG_AREA1 )
    if len(logger.handlers) < 1:
        logger.addHandler( stdoutHandler )

我不认为这是处理它的好方法。就个人而言,为了使用 python 日志记录模块登录 django,我在 views.py 中为我感兴趣的每个应用程序创建一个记录器,然后在每个视图函数中获取记录器。

from django.http import HttpResponse
from magic import makeLogger
from magic import getLogger

makeLogger('myLogName', '/path/to/myLogName.log')
def testLogger(request):
    logger = getLogger('myLogName')
    logger.debug('this worked')
    return HttpResponse('TEXT, html or WHATEVER')

这是一篇关于调试 django 的非常好的文章,涵盖了一些日志记录: http://simonwillison.net/2008/May/22/debugging/

【讨论】:

【参考方案6】:

回答为什么“Django 多次导入 settings.py”的问题:它没有。

您可能正在运行一个多进程/多线程 Web 服务器,它创建了几个 python 子解释器,其中每个子解释器从您的 django 应用程序导入代码一次。

在 django 测试服务器上进行测试,您应该会看到设置没有多次导入。

前段时间,我用我的第一个 django/apache 应用程序设计了一个不错的单例(更准确地说是 python borg 成语版本),然后我很快意识到是的,我创建了多个单例实例。 .

【讨论】:

【参考方案7】:

回答为什么“Django 多次导入 settings.py”的问题:它没有。

实际上,它确实被导入了两次(跳过第一个代码块直接进入它,但如果你有时间,请好好阅读):

http://blog.dscpl.com.au/2010/03/improved-wsgi-script-for-use-with.html

PS- 很抱歉恢复一个旧线程。

【讨论】:

【参考方案8】:

从 1.3 版开始,Django 使用标准 python 日志记录,并使用 LOGGING 设置进行配置(此处记录:1.3、dev)。

Django 日志记录参考:1.3、dev。

【讨论】:

【参考方案9】:

您也可以使用一次性中间件来获得类似的效果,而无需私有变量。请注意,这只会配置 Web 请求的日志记录 - 如果您想登录 shell 或命令运行,则需要找到不同的解决方案。

from django.conf import settings
from django.core.exceptions import MiddlewareNotUsed
import logging
import logging.handlers
import logging.config

__all__ = ('LoggingConfigMiddleware',)


class LoggingConfigMiddleware:
    def __init__(self):
        '''Initialise the logging setup from settings, called on first request.'''
        if hasattr(settings, 'LOGGING'):
            logging.config.dictConfig(settings.LOGGING)
        elif getattr(settings, 'DEBUG', False):
            print 'No logging configured.'
        raise MiddlewareNotUsed('Logging setup only.')

【讨论】:

【参考方案10】:

恢复一个旧线程,但我在使用带有 dictConfig format 的 Django 1.3 Python 日志记录时遇到重复消息。

disable_existing_loggers 消除了多个 settings.py 加载的重复处理程序/日志记录问题,但如果您没有在特定的 logger 上适当地指定 propagate 布尔值,您仍然可以获得重复的日志消息。即,确保为子记录器设置propagate=False。例如,

'loggers': 
    'django': 
        'handlers':['null'],
        'propagate': True,
        'level':'INFO',
    ,
    'django.request': 
        'handlers': ['console'],
        'level': 'ERROR',
        'propagate': False,
    ,
    'project': 
        'handlers': ['console', 'project-log-file'],
        'level': 'DEBUG',
        'propagate': True,
    ,
    'project.customapp': 
        'handlers': ['console', 'customapp-log-file'],
        'level': 'DEBUG',
        'propagate': False,
    ,

在这里,project.customapp 设置了propagate=False,这样它也不会被project 记录器捕获。 Django logging docs 一如既往的出色。

【讨论】:

这也适用于 Django 1.4。 同样适用于 1.6> 顺便说一句,感谢propagate=False 提示,它解决了我的问题。【参考方案11】:

要添加到 A Lee 帖子,python 日志记录文档说明了有关传播的内容:

Logger.propagate

如果计算结果为 false,则此记录器或其子记录器不会将记录消息传递给更高级别(祖先)记录器的处理程序。构造函数将此属性设置为 1。

这意味着如果 propagate == False 然后 child logger 不会将日志消息传递给它的 parent logger

【讨论】:

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

如何在 Python Django 中运行单元测试时禁用日志记录?

python Django日志记录设置

Django logging日志模块详解(日志记录模板配置)

如何在 Django 调试工具栏中使用日志记录?

为啥这个 Django 日志记录不起作用?

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