Django中的logging

对网站、微服务来说,log(日志)是比较重要的运维工具。 Django的log,主要是复用Python标准库中的logging模块,在settings.py中进行配置。 此外,也提供了一些独特的扩展。

完整示例

以下为settings.py的片段:

TIME_ZONE = 'Asia/Shanghai'

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{asctime} {module}.{funcName} {lineno:3} {levelname:7} => {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'verbose',
            'filename': '/tmp/django.log',
            'maxBytes': 4194304,  # 4 MB
            'backupCount': 10,
            'level': 'DEBUG',
        },
    },
    'loggers': {
        '': {
            'handlers': ['console', 'file'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
        'django': {
            'handlers': ['console', 'file'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
            'propagate': False,
        },
    },
}

设置时间与时区

在log中,通过在format设置{asctime},可以看到时间。 时间很可能是log最重要的一个属性。 而对中国用户来说,默认时区是个必须关注的问题,否则时间和当地时间差距较大。 需要在settings.py中,做TIME_ZONE配置。

TIME_ZONE = 'Asia/Shanghai'

有这个配置在,可以不用修改系统的配置。

formatters

    'formatters': {
        'verbose': {
            'format': '{asctime} {module}.{funcName} {lineno:3} {levelname:7} => {message}',
            'style': '{',
        },
    },

format

Formatter就是log格式化的样式。 这里只定义了一种formatter,那就是verbose

其它可选字段,详见logrecord

style

这里style选择{,是指{asctime}这种形式。 如果选择%,则是%(asctime)s这种形式。 还有一种选择,是$,是$asctime${asctime}这种形式。 详见Use of alternative formatting styles

datefmt

Django自带模块默认的格式化形式,打印出来,日期时间的形式大致是这样:16/Nov/2018 09:03:22。 相当于设置了下面的datefmt

    'formatters': {
        'default': {
            ...
            'style': '{',
            'datefmt': '%d/%b/%Y %H:%M:%S',
        },
    },

其中,各个组成部分的含义与其它可选内容见下表:

Directive Meaning Notes
%a Locale’s abbreviated weekday name.
%A Locale’s full weekday name.
%b Locale’s abbreviated month name.
%B Locale’s full month name.
%c Locale’s appropriate date and time representation.
%d Day of the month as a decimal number [01,31].
%H Hour (24-hour clock) as a decimal number [00,23].
%I Hour (12-hour clock) as a decimal number [01,12].
%j Day of the year as a decimal number [001,366].
%m Month as a decimal number [01,12].
%M Minute as a decimal number [00,59].
%p Locale’s equivalent of either AM or PM. (1)
%S Second as a decimal number [00,61]. (2)
%U Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0. (3)
%w Weekday as a decimal number [0(Sunday),6].
%W Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. (3)
%x Locale’s appropriate date representation.
%X Locale’s appropriate time representation.
%y Year without century as a decimal number [00,99].
%Y Year with century as a decimal number.
%z Time zone offset indicating a positive or negative time difference from UTC/GMT of the form +HHMM or -HHMM, where H represents decimal hour digits and M represents decimal minute digits [-23:59, +23:59].
%Z Time zone name (no characters if no time zone exists).
%% A literal ‘%’ character.

logging默认格式中,asctime的逗号,后面是三位毫秒(milliseconds),在以上格式中并不存在。 因此,如果修改为自定义格式,毫秒信息丢失。 如果要额外添加,需要指定{msecs:03d}

所以,如果需要毫秒信息,一般不改默认格式。

handlers

    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'verbose',
            'filename': '/tmp/django.log',
            'maxBytes': 4194304,  # 4 MB
            'backupCount': 10,
            'level': 'DEBUG',
        },
    },

formatter

formatter是指定一个格式化方式,也就是前面formatters定义的那些。 这里选择了前面定义的verboase

level

level是选择log的最低等级。 由低到高,列出如下,需要按需选择。

  • DEBUG: Low level system information for debugging purposes.
  • INFO: General system information.
  • WARNING: Information describing a minor problem that has occurred.
  • ERROR: Information describing a major problem that has occurred.
  • CRITICAL: Information describing a critical problem that has occurred.

class

class是指定处理log的类,在Python里logging.handlers中。

可选class列出如下,详见Useful Handlers

class 功能
StreamHandler 输出到Stream。通常用来打印到标准输出。
FileHandler 打印到文件。
NullHandler 不格式化也不打印。主要是为了避免No handlers could be found for logger XXX的设计。
WatchedFileHandler 自动重开log文件,配合别的会自动切分的log文件使用。
RotatingFileHandler 自动按大小切分的log文件。
TimedRotatingFileHandler 按时间自动切分的log文件。
SocketHandler 向Socket打log,基于TCP协议。
DatagramHandler 向Socket打log,基于UDP协议。
SysLogHandler 在Unix-like系统打印到remote或local的Unix syslog。
NTEventLogHandler 在Windows系统打印到微软的event log。
SMTPHandler 通过email发送log。
MemoryHandler 打印到内存buffer。
HTTPHandler 通过HTTP协议向服务器发送log。
QueueHandler 打log到Queue中,适合多进程(multiprocessing)场景。

这里主要选择了StreamHandler和RotatingFileHandler。 既能在./manage.py runserver时打印在前台,又能在部署后打印到文件,方便前期开发和后期运维。

此外,Django额外定义了一个handler——AdminEmailHandler,向管理员发送邮件。

    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'email_backend': 'django.core.mail.backends.filebased.EmailBackend',
            'include_html': True,
        }
    },

有需要可以添加,但要确保邮件SMTP服务器的通畅。

loggers

    'loggers': {
        '': {
            'handlers': ['console', 'file'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
        'django': {
            'handlers': ['console', 'file'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
            'propagate': False,
        },
    },

到了loggers模块,最重要的是选择设置哪些logger。 这里设置了''(root)和'django'。 前者是为了方便地使用logging,所以设了root的logger,这也是默认的logger。 后者是打印所有Django自身的logger。

Django额外提供的loggers扩展如下:

propagate是设定是否向父logger传播信息。 对root来说,这一条可有可无。 但在本例中,root被使用了,所以'django'logger必须设置为'propagate': False,,否则会打印两次。

其它

现在dictConfig只有一个version,那就是1

disable_existing_loggers默认是True,禁用已经存在的logger。 被禁用的logger仍然存在,只是不接收任何输入,包括其父logger也得不到信息 ——这种行为可能导致一些问题,因此这一条通常固定设为False

Django还支持一些Filters,在打log到指定目标时预先过滤一些内容。 这里不使用,也未介绍。

代码示例

import logging as log

log.debug('Hello %s', 'world')
log.info('I am here!')

配置好一切后,使用起来就是这么简单。

当然,这里是因为使用了root这个logger,才不需要使用loggging.getLogger()。 如果是写提供给别人使用的库或Django App,需要考虑更有层次的log策略。

参考


相关笔记