Django笔记三十之log日志记录详解

本文首发于公众号:Hunter后端
原文链接:Django笔记三十之log日志的记录详解

这一节介绍在 Django 系统里使用 logging 记录日志

以下是一个简单的 logging 模块示例,可以先预览一下,接下来会详细介绍各个模块的具体功能:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(message)s',
        }
    },
    'handlers': {
        'file_1': {
            'level': 'INFO',
            'filename': '/Users/hunter/python/log_path/file_1.log',
            'formatter': 'verbose',
            'class': 'logging.FileHandler',
        },
        'file_2': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': '/Users/hunter/python/log_path/file_2.log',
            'formatter': 'verbose',
        },
        'custom_file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': '/Users/hunter/python/log_path/custome_file.log',
            'formatter': 'verbose',
        }
    },
    'loggers': {
        '': {
            'handlers': ['file_1'],
            'level': 'INFO',
            'propagate': False,
        },
        'django': { 
            'handlers': ['file_2'],
            'level': 'INFO',
            'propagate': True,
        },
        'custom': {
            'handlers': ['custom_file'],
            'level': 'INFO',
            'propagate': False,
        }
    }
}

以下是本篇笔记全部内容:

  1. 模块总览
  2. Loggers
  3. Handlers
  4. Filters
  5. Formatters
  6. 日志记录方式
  7. logger 参数解析
  8. handler 参数解析
    1. RotatingFileHandler 配置
    2. TimedRotatingFileHandler 配置
    3. HttpHandler 基本配置
    4. SMTPHandler 基本配置
    5. AdminEmailHandler 基本配置
  9. formatter 参数解析
  10. 指定 logger 输出
  11. 日志配置示例

1、模块总览

在 Django 系统中,日志的记录也可以在 setting.py 中配置,key 为 logging,然后下面有几个主要的模块:

loggers、handlers、filters、formatters

系统接收到日志信息后,进入 logger,然后根据指定的 handler 列表发送到 handler 中

根据 handler 的处理方式,将信息写入文件、发送邮件或者其他方式

这些信息可以经过 filter 进行进一步的过滤,根据 formatter 的信息组织形式通过 handler 的处理方式进行处理

2、Loggers

Loggers 是学习日志系统的一个切入点,每个 logger 都是一个命名的桶,处理的信息可以作为日志写入到 logger 里

每一个 logger 都可以被配置一个日志等级,日志等级描述了 logger 记录的信息的严重程度,python 定义了如下几种日志等级:

  • DEBUG:低的、基于调试目的的系统信息
  • INFO:一般系统消息
  • WARNING:发生了小问题的信息
  • ERROR:发生了大问题的信息
  • CRITICAL:发生了严重的问题的信息

每个被写入 logger 的消息都被称为是一个 Log Record(日志记录)。

每个日志记录在被发送到 logger 的时候都有一个日志等级来表示信息的严重程度
比如:

logger.info("xxx")

这些日志记录应该包含一些有用的、包含了问题产生原因的信息

当一条消息被发送到 logger,消息的等级会和 logger 的日志等级做一个比较,只有当消息的等级大于或等于 logger 的记录等级时,消息才会被当前 logger 进行更多的处理

如果这条消息被 logger 接收,那么它会被发送到 Handlers

3、Handlers

我们可以理解 handler 是一个处理器,用来决定每天发送到 logger 的信息应该怎么处理,也就是日志的记录形式

比如说写入一个文件,发送邮件等

跟 Logger 一样,handler也有一个日志等级,只有当发送到 handler 的日志等级大于等于 handler 的日志记录时,handler 才会处理信息

一个 Logger 可以有多个 handler 处理器,每个 handler 都可以有自己的日志等级,因此可以根据信息的重要程度来决定不同的输出

比如你可以用一个 handler 把 ERROR 和 CRITICAL 等级的信息转发到服务页面,另一个 handler 记录所有的信息到一个文件,用作后续的分析

4、Filters

过滤器常被用来提供额外的控制,处理从 logger 到 handler 的日志记录

理论上来说,任何日志消息只要满足了日志等级的要求,都会被发送到 handler 处理,如果加了一个 filter 过滤器,你可以在日志处理上添加额外的标准

比如说你可以添加一个过滤器,只允许某个特定来源的 ERROR 等级的信息被处理

filter 也可以用来修改消息的严重等级,比如一些特定的条件被满足的情况下,你可以将ERROR等级的日志降级为 WARNING

在本篇笔记中,将不介绍 filter 的使用方法,因为能简单就简单一点,暂时不用那么多配置

5、Formatters

格式化,一个日志记录需要被渲染成一个文本,formatter 提供了一些格式器的属性,格式化器由一些 LogRecord 的属性值组成,你也可以自己定义一个属性

6、日志记录方式

当你配置了 loggers,handlers,filters 和 formatters 之后,你可以先获取一个 logger 的实例,然后通过 logger 来记录日志

以下是使用示例:

import logging

logger = logging.getLogger(__name__)

def my_view(request):
    logger.info("this is a log")

这个在调用 my_view 的时候,系统就会记录一条日志

如果是其他等级的日志记录,则会是:

logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()

以下是对日志的记录流程汇总一下:

  • 当有一条日志信息需要被记录,然后会被发送到对应的 logger
  • 然后 logger 根据指定的 handler 被发送到对应的 handler 处理器
  • 在 handler 中会根据日志的等级或者定义的 filter 进行一定的过滤
  • 最终将符合条件的日志信息根据 formatter 的格式定义,将最终形成的日志信息,进行 console 操作、记录到文件、或者发送邮件等操作

在笔记开篇的 logging 示例中,日志的配置在这个 dict 里编写的顺序和消息处理的顺序是相反的

这里没有设置 filter 的操作,所以消息的处理就是从 logger 到 handler 再到 formatter

我们手动在接口里写入一个日志消息,分别在 urls.py 和 views.py 里如下定义:

# blog/urls.py
from django.urls.conf import path
from blog.views import time_view


urlpatterns = [
    path("curr_time", time_view),
]
# blog/views.py
import datetime
from django.http import HttpResponse

import logging

logger = logging.getLogger(__name__)

def time_view(request):
    now = datetime.datetime.now()
    html = "<h1>now: %s</h1>" % now
    logger.info("this is a log !")
    return HttpResponse(html)

启动系统后,在浏览器中访问 http://localhost:9898/blog/curr_time,可以看到定义的日志目录下已经写入了数据:file_1.log 和 file_2.log

打开这两个日志文件,可以看到 loggers 下空字符串指定的 logger 对应的处理器写入的 file_1.log 写入的内容如下:

INFO this is a log ! xxxx
INFO "GET /blog/curr_time HTTP/1.1" 200 40 xxxx

其中包含接口访问信息和我们在接口里自定义的 'this is a log !' 信息

在 file_2.log 中,则只有接口的访问信息:

INFO  200 40 xxxx

在实例化 logger 的时候,如果不指定 logger 的名称,那么则会默认写入我们定义的空字符串下的 logger

不指定 logger 名称的意思即为,getLogger 的时候不指定 logger 的参数:

logger = logging.getLogger(__name__)

7、logger 参数解析

在这里 loggers 里设置两个 key,一个为空字符串,一个是 django。

我们可以理解 key 为 django' 这个 logger 是一个固定的值,会接收到所有来自系统的日志信息,比如一些接口的请求信息,但是不包括用户自定的 logger 输出。

空字符串这里的 logger,可以接收到用户自定义的 logger 输出,也可以接收到一些接口的请求信息,但是这个需要 propagate 的配置

在 loggers 的配置里面:

    'loggers': {
        '': {
            'handlers': ['file_1'],
            'level': 'INFO',
            'propagate': False,
        },
        'django': {
            'handlers': ['file_2'],
            'level': 'INFO',
            'propagate': True,
        }
    }

有如下几个参数:
handlers 是指定消息处理器的,value 是一个列表,可以指定多个处理器,比如说一条消息,你可以同时指定写入文件和发送邮件,或者写入不同的文件

level 参数表示日志的等级,这里设置的是 INFO 等级,如果接收到的消息的等级小于 INFO,那么就会不处理,大于等于 INFO 才会被发送到 handler 处理器中处理

propagate 参数意义可以理解为是否传递传递,在这两个 logger 里,如果 django 这个 logger 的 propagate 的值设为了 True,django 这个 logger 的消息是可以向 空字符串设置的 logger 传递的

换句话说,django 接收到的所有消息都会给空字符串的 logger 再发一遍,使用它的 logger 再进行一遍处理,
如果 propagate 设为了 False,那么空字符串的 logger 仅能接收到用户自定义的消息

8、handler 参数解析

当一条消息从 logger 被发送到 handler,handlers 参数也可以定义多个,通过不同的 key 来区分

在每个 handler 下我们这里设置了四个值:

level 设置了 handler 处理的日志等级,只有当发送过来的日志的等级大于等于该等级时,这个 handler 才会处理

class 设置了日志处理的方式,这里我们的值为 logging.FileHandler,表示是文件处理方式

此外还有比如 StreamHandler 输出到 Stream 打印到标准输出,还有 HttpHandler 通过HTTP 协议向服务器发送 log, 还有 SMTPHandler 会通过 email 发送log

filename 指定输出的日志地址,前面我们的 class 定义为向文件输出,那么这里的 filename 就定义了输出的文件的地址

formatter 则是指定下一级日志文本的输出格式处理的 formatter

日志文件处理策略

对于日志文件,如果系统一直运行,那么则会存在一个问题,那就是日志文件越来越大,这个对于系统的存储和我们查找日志都是不合适的

因此接下来我们新增几个参数用来制定日志文件的处理策略

maxBytes,这个定义了一个日志文件最大的字节数,如果写满了就会新开一个文件继续写而不是继续在原有文件继续增加内容

如果我们需要设置一个文件最大为5M,就可以设为 5 * 1024 * 1024

backupCount,最大的日志文件数量,当文件的个数超出了我们定义的,则会删除最早的日志文件,只保留 backupCount 个日志文件

但是使用上面这两个参数的话,class 的值就得换成 logging.handlers.RotatingFileHandler

接下来介绍几种 handler 的信息处理方式

1.RotatingFileHandler 配置

rotate 的是定期调换位子,轮换的意思

RotatingFileHandler 的作用是根据文件的大小决定是否写入新文件,以下是一个示例:

        'custom_file': {
            'level': 'INFO',
            'filename': '/home/hunter/python/log_path/custom.log',
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'verbose',
            'maxBytes': 5 * 1024 * 1024,
            'backupCount': 10
        }

这个示例表示是将日志写入文件,每个文件最大容量为 5 * 1024 * 1024,即 5M,当日志写入一个文件满了 5M 之后,将会新开一个文件继续写入日志信息,文件夹下保留最新的 10 个文件。

这里新增了两个配置项

backupCount 表示最多保留日志文件的个数

maxBytes 表示每个日志文件最大的存储容量

2.TimedRotatingFileHandler 配置

TimedRotatingFileHandler 表示是根据时间间隔来决定是否写入新文件,以下是示例:

        'time_file': {
            'level': 'INFO',
            'filename': '/home/hunter/python/log_path/custom.log',
            'class': 'logging.handlers.TimedRotatingFileHandler',  # 记录时间
            'formatter': 'verbose',
            'backupCount': 3,
            'when': 'M',
            'interval': 3,
        }

当 handler 的 class 的值为这个的时候,表示的是根据时间来决定是否写入新文件,上一个是根据文件的容量大小来定的

这里新增了两个配置项,

一个是 when,表示的时间间隔的单位,S为秒,M为分钟,H为小时,D或者 MIDNIGHT为天,W0-W6为从周一到周日某个周几开始间隔一周

另一个是 interval,间隔时间的倍数

日志换新文件继续写入的时间为 when * interval

3.HttpHandler 基本配置

这个配置表示是如果来了需要处理的日志消息就调用一个 HTTP 接口,这里我们可以只做一个示例:

        'http_handler': {
            'level': 'INFO',
            'class': 'logging.handlers.HTTPHandler',
            'formatter': 'verbose',
            'host': '192.168.1.8:9898',
            'url': '/test_url',
            'method': 'POST',
        },

这个地方,多了几个配置项

host 表示需要调用接口的 ip 和 端口

url 表示调用的接口路径

method 表示调用的方法

4.SMTPHandler 基本配置

这个配置用于发送邮件,如果日志消息发送到这个配置的 handler,系统会根据邮件的配置系统发送邮件给指定的邮箱

以下是一个使用示例:

        'email': {
            'level': 'WARNING',
            'class': 'logging.handlers.SMTPHandler',
            'mailhost': ('smtp.163.com', 25),
            'fromaddr': '[email protected]',
            'toaddrs': '[email protected]',
            'subject': '系统出错啦!!!',
            'credentials': ('[email protected]', 'JBD******'),
            'timeout': 20
        },

在这个配置中,多的配置项的介绍如下:

mailhost 是系统发送邮件的邮箱的主机和端口,这里我们配置的是 163 邮箱

fromaddr 是从哪个邮箱发出来,我们可以创建一个163邮箱然后指定该值

toaddrs 是发送到哪个邮箱,即日志消息的邮件接收地址

subject 是我们发送邮件的标题,而邮件的正文内容即为我们在 logger.warning("报错信息") 中输入的信息

credentials 是163邮箱的验证信息,两个值,前一个值与 fromaddr 保持一致,后面的是一串验证码,是163邮箱开启 SMTP 服务之后163邮箱系统页面给我们的一串授权密码,这个可以自己去了解一下

这样配置好之后,在 logger 的 handler 列表指定这个 handler,然后通过 logger.warning("报错信息") 即可触发这个邮件发送的功能

5.AdminEmailHandler 基本配置

这个配置也是用于日志发送邮件,但是是复用 Django 的默认邮箱的功能

在 logging 中的配置是:

        'mail_admins': {
            'level': 'WARNING',
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        },

但是这个还需要一些额外的在 settings.py 中的邮箱配置,相当于是复用 Django 系统的功能

以下是 settings.py 中邮箱的配置项:

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'   # 163 邮箱的配置地址
EMAIL_PORT = 465  # SMTP 端口
EMAIL_HOST_USER = '[email protected]'   #这个是用来发送邮件的邮箱,与最后一个填写的邮箱地址一致
EMAIL_HOST_PASSWORD = 'JBDM******'  #这里就是前面提到的授权密码
EMAIL_USE_SSL = True
EMAIL_FROM = SERVER_EMAIL = '[email protected]' # 这个是发送邮件的地址,与上面的 163邮箱相同即可
ADMINS = [
    ['Hunter', '[email protected]'],
]  # 邮件接收地址

上面的参数都配置好之后也可以日志触发邮件了。

9、formatter 参数解析

formatter 的参数就简单一点,通过不同的 key 来区分不同的 formatter,其下设置一个 format 参数即可对信息进行格式化处理

    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(message)s',
        }
    },

在示例中只设置了 levelname 和 message 两个参数,levelname 即为该日志消息的等级,message为消息内容

对于请求接口的 message 信息,返回的内容是固定的,比如前面的示例:

"GET /blog/curr_time HTTP/1.1" 200 40

前面是接口的请求方式、接口路径和HTTP协议,然后是接口返回的状态码,这里是 200,后面跟着的 40 这个数字则是接口返回的字符长度

如果是用户在系统里手动写入的 message,则是定义的什么内容,输出的就是什么内容

对于 format 的定义的参数还有很多,以下是几个常用的汇总:

参数名称 参数用法 含义
levelname %(levelname)s 日志等级
message %(message)s 消息内容
asctime %(asctime)s 时间,格式为'2022-01-01 00:00:00,000'
pathname %(pathname)s 日志输出所在文件的全路径
filename %(filename)s 日志输出所在的文件名
module %(module)s 日志输出所在的模块,可以理解成不带后缀的文件名
name %(name)s 调用日志使用的名称,logging.getLogger(name)时为从模块到函数,比如 blog.views
funcName %(funcName)s 日志输出的函数名称
lineno %(lineno)d 日志输出所在的文件的行数
process %(process)d 进程id
processName %(processName)s 进程名称
thread %(thread)d 线程id
threadName %(threadName)s 线程名称

10、指定 logger 输出

之前我们设定的用户手动输入的日志被传送给了 key 为空字符串下的 logger,如果我们想把某一些日志信息专门输出到某个文件怎么处理呢?

在获取 logger 的时候就需要根据 logger 的 key 来指定对应的 logger,比如我们新建一个名为 custom 的 logger 和 对应的 handler,然后输出的地方指定即可,如下:

        'custom': {
            'handlers': ['custom_file'],
            'level': 'INFO',
            'propagate': False,
        }

指定 logger 输出:

import datetime
from django.http import HttpResponse

import logging

custom_logger = logging.getLogger("custom")  # 对应 logging 配置中的 key 为 custom 的 logger 


def time_view(request):
    now = datetime.datetime.now()
    html = "<h1>now: %s</h1>" % now
    custom_logger.info("this is a custom log")
    return HttpResponse(html)

这样在对应的地方就可以实现专门的日志输出到专门的文件了。

11、日志配置示例

接下来我们实现这样一个日志配置的功能:

  1. 实现用户所有普通的手动输出都写入一个 manual.log 文件
  2. 所有接口的请求数据都输入到一个 request.log 文件
  3. 设置一个单独的日志输出,可以输出到指定文件
  4. 所有 INFO 级别的日志都输出到文件,高于 INFO 的都发送邮件通知指定联系人
  5. 对于日志文件要求每个文件最大容量为 50M,且文件夹下每个类型的日志最多只有10个
  6. 日志的信息结构为:日志等级-时间-日志输出所在文件名-日志输出所在函数名-日志输出所在文件的行数-消息内容

以下是实现上面这个功能的 logging 配置:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(filename)s %(funcName)s %(lineno)d %(message)s',
        }
    },
    'handlers': {
        'manual_file': {
            'level': 'INFO',
            'filename': '/Users/hunter/python/log_path/manual.log',
            'formatter': 'verbose',
            'class': 'logging.handlers.RotatingFileHandler',
            'maxBytes': 5 * 1024 * 1024,
            'backupCount': 10
        },
        'request_file': {
            'level': 'INFO',
            'filename': '/Users/hunter/python/log_path/request.log',
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'verbose',
            'maxBytes': 5 * 1024 * 1024,
            'backupCount': 10
        },
        'custom_file': {
            'level': 'INFO',
            'filename': '/Users/hunter/python/log_path/custom.log',
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'verbose',
            'maxBytes': 5 * 1024 * 1024,
            'backupCount': 10
        },
        'email': {
            'level': 'WARNING',
            'class': 'logging.handlers.SMTPHandler',
            'mailhost': ('smtp.163.com', 25),
            'fromaddr': '[email protected]',
            'toaddrs': '[email protected]',
            'subject': '系统出错啦!!!',
            'credentials': ('[email protected]', 'JBD*******'),
            'timeout': 20
        },
    },
    'loggers': {
        '': {
            'handlers': ['manual_file', 'email'],
            'level': 'INFO',
            'propagate': False,
        },
        'django': {
            'handlers': ['request_file'],
            'level': 'INFO',
            'propagate': True,
        },
        'custom': {
            'handlers': ['custom_file'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}

然后我们定义一个接口内容:

import datetime
from django.http import HttpResponse, JsonResponse

import logging

logger = logging.getLogger(__name__)
custom_logger = logging.getLogger("custom")


def time_view(request):
    now = datetime.datetime.now()
    html = "<h1>now: %s</h1>abc\nabc" % now
    logger.info("this is a log !")
    custom_logger.info("this is a custom log")
    logger.warning("报错啦!!!")
    return HttpResponse(html)

调用这个接口即可发现实现了我们想要的功能啦!

如果想获取更多后端相关文章,可扫码关注阅读:

热门相关:超武穿梭   天启预报   天启预报   薄先生,情不由己   重生之至尊千金