要想放心的在生产环境下使用 Django,其 Logging 的配置和使用是不可或缺的。

这篇文章整体参考 django-logging-right-way 这片文章即可,其他的辅助查看 logging 文档 就可理解不少,这里将我觉得几个值得注意的点摘取出来。

知识点

  • setting 里配置字典 LOGGING 即可配置 logging

  • 可以配置多种 handlers 如 console、file、sentry、ELK

  • logger = logging.getLogger(__name__) 是以当前模块名获取在 LOGIING 里名如 apps.article.views loggers

  • 如果在 LOGGING 没有配置该模块名的 logger,默认采用空名的 root logger 如 '': {'level': 'WARNING', 'handlers': ['console',]},如果没有 root logger 将不会记录,如果存在 root logger 其格式里的 %(name) 会是模块名,这一点比 %{pathname} 信息更加简单一些。

  • 如果同时存在 root logger 和获取的模块名的 logger,两个 logger 都会记录。可以在需要模块名 logger 的配置下加上 'propagate': False 阻止 log 的继续传播。

  • logger 的配置里的 handlers 是数组,也就是可以允许进行多个 handler 同时进行记录如:想将记录发到日志服务器,同时也想输出到控制台方便调试可以 'handlers': ['console', 'sentry'],

  • django 采用的是 Python 内置的 logging 模块,日志记录的格式占位符的意义参考 logrecord-attributes

  • 要想 override 默认 runserver 的请求日志,可以在 LOGGING 里重新配置 django.server 的 logger

  • log 的级别,loggers 里定义的级别决定其记录的下限级别,低于该级别的不再记录

    • debug: Info not needed for regular operation, but useful in development.
    • info: Info that’s helpful during regular operation.
    • warning: Info that could be problematic, but is non-urgent.
    • error: Info that is important and likely requires prompt attention.
    • critical: I don’t find myself using this in practice, but if you need one higher than error, here it is
  • logger 除了对应等级的 debug, info 等等记录,还可以记录异常,如 logger.exception(exception),记录时候可以使用函数提供的占位符如 logger.info("The value of var is %s", var)

  • logger 的 handler 使用 ELK 和 sentry 收集日志。使用 Google 可以得到很多不错的文章,当容器化 django 应用时候我推荐的是将日志输出到 console 然后由 k8s 收集,之后使用在 node 上使用 filebeat 收集发送到 Kibana 上,而不是一个 pod 使用一个 Logstash 发送。

使用 logging 记录 SQL

在开发过程中经常遇到需要记录 SQL 语句,分析 ORM 的情况,除了直接设置 MySQL 的 loglevel 。在 Django 里其实可以使用 logger 来调试打印 SQL 语句。

方法也很简单,在 setting 文件里加上下面的语句。

1
2
3
4
5
# using for logging sql in console
DISP_SQL = False

if DISP_SQL:
    LOGGING['loggers']['django.db.backends'] = {'level': 'DEBUG', 'handlers': ['console'],}

这里用到了上面给出的 console handler,相比 使用 MySQL 的日志文件记录大量的 SQL 语句,而且更改配置后需要重启 MySQL 服务,使用起来非常不灵活。如果使用 Django 的 logger 来记录,可以随时关闭打开其记录,而且可以方便的更改其输出到文件或者控制台,输出到控制台时候还可以方便与其他调试信息联调。

参考 LOGGING 和用法

这里给出 setting.py 里我常用的 logging 的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(name)s:%(lineno)d %(process)d-%(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(asctime)s %(message)s'
        },
        'line': {
            'format': '%(levelname)s %(asctime)s %(name)s:%(lineno)d %(message)s'
        }
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'line'
        },
        'file': {
            'level': 'WARNING',
            'formatter': 'verbose',
            'class': 'logging.FileHandler',
            'filename': os.path.join(BASE_DIR, "log/error.log")
        },
    },
    'loggers': {
        # root logger
        '': {
            'handlers': ['console'],
            'level': 'INFO'
        },
        'alert': {
            'handlers': ['console', 'file'],
            'level': 'WARNING'
        },
        'app.article.views': {
            'handlers': ['console'],
            'level': 'INFO',
            # required to avoid double logging with root logger
            'propagate': False,
        },
    }
}

在使用的时候就可以这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 'app.article.views'
import logging

# 获取到的是 app.article.views
logger = logging.getLogger(__name__)

# 获取到的是 alert
logger_alert = logging.getLogger("alert")

# 没有改名称的logger,所以获取到的是 root logger,其 %{name} 为传入的字符串 someting
logger_someting = logging.getLogger("someting ")

有时候多个地方需要使用某个 logger 但是,其 format 里的 %{name} 是 logger 名,无法定位产生 log 的地方,不像使用 root 时候那样方便,但是也不能使用 root。这样就可以使用下面的方法,在获取 logger 之后重新命名下。

1
2
logger = logging.getLogger('alert')
logger.name = __name__