Python:使用logging模块记录日志

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SCF_1104/article/details/84031250

logging模块简介

logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;

相比print(),具备如下优点:

     1).可以通过设置不同的日志等级,在release版本中只输出重要信息,而不必显示大量的调试信息;

     2).print()将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging则可以由开发者决定将信息输出到什么地方,以及怎么输出;

简单使用

import logging

logging.critical('等级我是天下第一,不服来辩!')
logging.error('呦呦呦,我说自己是第二,没人敢说自己是第一!')
logging.warning('前面两个别吵了,让我这第三情何以堪!')
logging.info('各位少侠,别来无恙,区区第四,不值一提!')
logging.debug('我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!')

输出:

CRITICAL:root:等级我是天下第一,不服来辩!
ERROR:root:呦呦呦,我说自己是第二,没人敢说自己是第一!
WARNING:root:前面两个别吵了,让我这第三情何以堪!

从上面输出可以看出只打印出来前三条记录,这是为什么呢?因为默认生成的root logger的level等级是logging.WARNING,低于该级别的就不输出了。

当我们设置了输出 level,系统便只会输出 level 数值大于或等于该 level 的的日志结果,例如我们设置了输出日志 level 为 INFO,那么输出级别大于等于 INFO 的日志。

关于各个等级高低介绍见表格:

日志等级
等级 介绍 数值
CRITICAL 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行 50
FATAL 打印的也是critical级别,致命错误,使用很少 50
ERROR 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能 40
WARNING 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如:磁盘空间低”),这个软件还能按预期工作 30
WARN 是WARNING的简写模式,功能和等级与WARNING一样,在Python3中已被废弃 30
INFO 打印info,warning,error,critical级别的日志,确认一切按预期运行 20
DEBUG 打印全部的日志,详细的信息,通常只出现在诊断问题上 10
NOTSET 如果需要显示低于WARNING级别的内容,可以引入NOTSET级别来显示: 0

下面我们把之前的代码加工一下,增加一些配置,使输出的日志信息更易读。

# -*- coding:utf-8 -*-
import logging

logging.basicConfig(level=logging.DEBUG,format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

logger.critical('等级我是天下第一,不服来辩!')
logger.error('呦呦呦,我说自己是第二,没人敢说自己是第一!')
logger.warning('前面两个别吵了,让我这第三情何以堪!')
logger.info('各位少侠,别来无恙,区区第四,不值一提!')
logger.debug('我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!')

代码分析:

在这里我们首先引入了 logging 模块,然后进行了一下基本的配置,这里通过 basicConfig 配置了 level 信息和 format 信息,这里 level 配置为 DEBUG ,另外这里指定了 format 格式的字符串,包括 asctime、name、levelname、message 四个内容,分别代表运行时间、模块名称、日志级别、日志内容,这样输出内容便是这四者组合而成的内容了,这就是 logging 的全局配置。

接下来声明了一个 Logger 对象,它就是日志输出的主类,调用对象的 info() 方法就可以输出 INFO 级别的日志信息,调用 debug() 方法就可以输出 DEBUG 级别的日志信息,非常方便。在初始化的时候我们传入了模块的名称,这里直接使用 __name__ 来代替了,就是模块的名称,如果直接运行这个脚本的话就是 __main__,如果是 import 的模块的话就是被引入模块的名称,这个变量在不同的模块中的名字是不同的,所以一般使用 __name__ 来表示就好了.

看下输出结果:

2018-11-14 11:27:33,635 - __main__ - CRITICAL - 等级我是天下第一,不服来辩!
2018-11-14 11:27:33,635 - __main__ - ERROR - 呦呦呦,我说自己是第二,没人敢说自己是第一!
2018-11-14 11:27:33,635 - __main__ - WARNING - 前面两个别吵了,让我这第三情何以堪!
2018-11-14 11:27:33,635 - __main__ - INFO - 各位少侠,别来无恙,区区第四,不值一提!
2018-11-14 11:27:33,635 - __main__ - DEBUG - 我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!

上面这写只是 logging 模块的一小部分功能,接下来我们首先来全面了解一下 basicConfig 的参数都有哪些:

  • filename:即日志输出的文件名,如果指定了这个信息之后,实际上会启用 FileHandler,而不再是 StreamHandler,这样日志信息便会输出到文件中了。
  • filemode:这个是指定日志文件的写入方式,有两种形式,一种是 w,一种是 a,分别代表清除后写入和追加写入。
  • format:指定日志信息的输出格式,即上文示例所示的参数,详细参数可以参考:docs.python.org/3/library/l…,部分参数如下所示:
    • %(levelno)s:打印日志级别的数值。
    • %(levelname)s:打印日志级别的名称。
    • %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]。
    • %(filename)s:打印当前执行程序名。
    • %(funcName)s:打印日志的当前函数。
    • %(lineno)d:打印日志的当前行号。
    • %(asctime)s:打印日志的时间。
    • %(thread)d:打印线程ID。
    • %(threadName)s:打印线程名称。
    • %(process)d:打印进程ID。
    • %(processName)s:打印线程名称。
    • %(module)s:打印模块名称。
    • %(message)s:打印日志信息。
  • datefmt:指定时间的输出格式。
  • style:如果 format 参数指定了,这个参数就可以指定格式化时的占位符风格,如 %、{、$ 等。
  • level:指定日志输出的类别,程序会输出大于等于此级别的信息。
  • stream:在没有指定 filename 的时候会默认使用 StreamHandler,这时 stream 可以指定初始化的文件流。
  • handlers:可以指定日志处理时所使用的 Handlers,必须是可迭代的。 

以上这些就是basicConfig 的一些全局的配置,感兴趣的小伙伴可以在代码中尝试下。

另外我们同样可以使用 Formatter、Handler 进行更灵活的处理,下面我们来了解一下。

Handler

下面我们先来了解一下 Handler 的用法,看下面的实例:

# -*- coding:utf-8 -*-
import logging

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)
handler = logging.FileHandler('log.log')
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.critical('等级我是天下第一,不服来辩!')
logger.error('呦呦呦,我说自己是第二,没人敢说自己是第一!')
logger.warning('前面两个别吵了,让我这第三情何以堪!')
logger.info('各位少侠,别来无恙,区区第四,不值一提!')
logger.debug('我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!')

这里没有再使用 basicConfig 全局配置,而是先声明了一个 Logger 对象,然后指定了其对应的 Handler 为 FileHandler 对象,然后 Handler 对象还单独指定了 Formatter 对象单独配置输出格式,最后给 Logger 对象添加对应的 Handler 即可,最后可以发现日志就会被输出到 log.log 中,内容如下:

2018-11-14 11:52:43,444 - __main__ - CRITICAL - 等级我是天下第一,不服来辩!
2018-11-14 11:52:43,445 - __main__ - ERROR - 呦呦呦,我说自己是第二,没人敢说自己是第一!
2018-11-14 11:52:43,445 - __main__ - WARNING - 前面两个别吵了,让我这第三情何以堪!
2018-11-14 11:52:43,445 - __main__ - INFO - 各位少侠,别来无恙,区区第四,不值一提!
2018-11-14 11:52:43,445 - __main__ - DEBUG - 我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!

另外我们还可以使用其他的 Handler 进行日志的输出,logging 模块提供的 Handler 有:

  • StreamHandler:logging.StreamHandler;日志输出到流,可以是 sys.stderr,sys.stdout 或者文件。
  • FileHandler:logging.FileHandler;日志输出到文件。
  • BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式。
  • RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚。
  • TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件。
  • SocketHandler:logging.handlers.SocketHandler;远程输出日志到TCP/IP sockets。
  • DatagramHandler:logging.handlers.DatagramHandler;远程输出日志到UDP sockets。
  • SMTPHandler:logging.handlers.SMTPHandler;远程输出日志到邮件地址。
  • SysLogHandler:logging.handlers.SysLogHandler;日志输出到syslog。
  • NTEventLogHandler:logging.handlers.NTEventLogHandler;远程输出日志到Windows NT/2000/XP的事件日志。
  • MemoryHandler:logging.handlers.MemoryHandler;日志输出到内存中的指定buffer。
  • HTTPHandler:logging.handlers.HTTPHandler;通过”GET”或者”POST”远程输出到HTTP服务器。

下面我们使用三个 Handler 来实现日志同时输出到控制台、文件、HTTP 服务器:

# -*- coding:utf-8 -*-
import logging
from logging.handlers import HTTPHandler
import sys

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)

# FileHandler 将日志输出到文件log.log
file_handler = logging.FileHandler('log.log')
file_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# StreamHandler 将日志输出到控制台
stream_hanlder = logging.StreamHandler(sys.stdout)
stream_hanlder.setLevel(level=logging.DEBUG)
logger.addHandler(stream_hanlder)

# HTTPHandler 将日志输出到HTTP服务器
http_handler = HTTPHandler(host='localhost:8080', url='log', method='POST')
logger.addHandler(http_handler)

logger.critical('等级我是天下第一,不服来辩!')
logger.error('呦呦呦,我说自己是第二,没人敢说自己是第一!')
logger.warning('前面两个别吵了,让我这第三情何以堪!')
logger.info('各位少侠,别来无恙,区区第四,不值一提!')
logger.debug('我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!')

运行之前我们需要先启动 HTTP Server,并运行在 8080 端口,其中 log 接口是用来接收日志的接口。

我是在Windows环境搭建的http-server服务器,具体搭建教程看链接:http://www.fefuns.com/2018/07/03/224/

运行之后控制台输出会输出如下内容:

等级我是天下第一,不服来辩!
呦呦呦,我说自己是第二,没人敢说自己是第一!
前面两个别吵了,让我这第三情何以堪!
各位少侠,别来无恙,区区第四,不值一提!
我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!

log.log文件会写入下面的内容:

2018-11-14 14:51:09,991 - __main__ - CRITICAL - 等级我是天下第一,不服来辩!
2018-11-14 14:51:11,038 - __main__ - ERROR - 呦呦呦,我说自己是第二,没人敢说自己是第一!
2018-11-14 14:51:12,048 - __main__ - WARNING - 前面两个别吵了,让我这第三情何以堪!
2018-11-14 14:51:13,057 - __main__ - INFO - 各位少侠,别来无恙,区区第四,不值一提!

HTTP Server 会收到控制台输出的信息。

但是这儿本人有个疑问,就是HTTP Server接收到的日志信息存放在哪儿了?还望懂的大神告知!

这样一来,我们就通过设置多个 Handler 来控制了日志的多目标输出。

另外值得注意的是,在这里 StreamHandler 对象我们没有设置 Formatter,因此控制台只输出了日志的内容,而没有包含时间、模块等信息,而 FileHandler 我们通过 setFormatter() 方法设置了一个 Formatter 对象,因此输出的内容便是格式化后的日志信息。

另外每个 Handler 还可以设置 level 信息,最终输出结果的 level 信息会取 Logger 对象的 level 和 Handler 对象的 level 的交集。

Format

在进行日志格式化输出的时候,我们可以不借助于 basicConfig 来全局配置格式化输出内容,可以借助于 Formatter 来完成,下面我们再来单独看下 Formatter 的用法:

# -*- coding:utf-8 -*-
import logging

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.WARNING)

stream_hanlder = logging.StreamHandler()
formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
stream_hanlder.setFormatter(formatter)
logger.addHandler(stream_hanlder)

logger.critical('等级我是天下第一,不服来辩!')
logger.error('呦呦呦,我说自己是第二,没人敢说自己是第一!')
logger.warning('前面两个别吵了,让我这第三情何以堪!')
logger.info('各位少侠,别来无恙,区区第四,不值一提!')
logger.debug('我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!')

在这里我们指定了一个 Formatter,并传入了 fmt 参数,这样就指定了日志结果的输出格式,当然我们还可以设置其他的参数,比如时间格式参数datefmt等,然后 handler 通过 setFormatter() 方法设置此 Formatter 对象即可,输出结果如下:

2018-11-14 15:19:23,733 - __main__ - CRITICAL - 等级我是天下第一,不服来辩!
2018-11-14 15:19:23,734 - __main__ - ERROR - 呦呦呦,我说自己是第二,没人敢说自己是第一!
2018-11-14 15:19:23,734 - __main__ - WARNING - 前面两个别吵了,让我这第三情何以堪!

由上可见,我们可以为每个 Handler 单独配置输出的格式,非常灵活。

捕获 Traceback

如果遇到错误,我们更希望报错时出现的详细 Traceback 信息,便于调试,利用 logging 模块我们可以非常方便地实现这个记录,我们用一个实例来感受一下:

# -*- coding:utf-8 -*-
import logging

formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)

# FileHandler 将日志输出到日志文件log.log
file_handler = logging.FileHandler('log.log')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# StreamHandler 将日志输出到控制台
stream_hanlder = logging.StreamHandler()
stream_hanlder.setFormatter(formatter)
logger.addHandler(stream_hanlder)

logger.critical('等级我是天下第一,不服来辩!')
try:
    result = 10 / 0    # 此处执行一条非法的语法
except Exception:
    logger.error('呦呦呦,我说自己是第二,没人敢说自己是第一!', exc_info=True)

logger.warning('前面两个别吵了,让我这第三情何以堪!')
logger.info('各位少侠,别来无恙,区区第四,不值一提!')
logger.debug('我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!')

这里我们在 error() 方法中添加了一个参数,将 exc_info 设置为了 True,这样我们就可以输出执行过程中的信息了,即完整的 Traceback 信息。

输出到日志文件和控制台的运行结果如下:

2018-11-14 15:49:28,458 - __main__ - CRITICAL - 等级我是天下第一,不服来辩!
2018-11-14 15:49:28,458 - __main__ - ERROR - 呦呦呦,我说自己是第二,没人敢说自己是第一!
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/test2.py", line 31, in <module>
    result = 10 / 0
ZeroDivisionError: division by zero
2018-11-14 15:49:28,458 - __main__ - WARNING - 前面两个别吵了,让我这第三情何以堪!
2018-11-14 15:49:28,458 - __main__ - INFO - 各位少侠,别来无恙,区区第四,不值一提!
2018-11-14 15:49:28,458 - __main__ - DEBUG - 我排名第五,不接受反驳!我不会说自己是倒数第一的,哼!

从输出信息可以看到,error()将代码执行过程的报错的信息记录下来了,这样我们就能非常方便地排查。

logging模块本文就介绍到这,很多内容转载至:https://cuiqingcai.com/6080.html

请查看原文,继续学习!

猜你喜欢

转载自blog.csdn.net/SCF_1104/article/details/84031250