日志logging

一、日志概述

日志作用

不管是在项目开发还是测试过程中,项目运行一旦出现问题日志信息就非常重要了。日志是定位问题的重要手段,就像侦探人员要根据现场留下的线索来推断案情。

总的来说,日志的作用:

  1、程序调试

  2、了解软件运行的情况,看软件是否正常

  3、软件的故障分析和问题定位

二、logging模块简介

1. logging模块的日志级别

logging模块默认定义了以下几个日志等级,它允许开发人员自定义其他日志级别,但是这是不被推荐的,尤其是在开发供别人使用的库时,因为这会导致日志级别的混乱。

日志等级(level)描述
DEBUG 最详细的日志信息,典型应用场景是 问题诊断
INFO 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
WARNING 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的
ERROR 由于一个更严重的问题导致某些功能不能正常运行时记录的信息
CRITICAL 当发生严重错误,导致应用程序不能继续运行时记录的信息

上述日志等级从上往下依次升高,即DEBUG<NFO<WARNING<ERROR<CRITICAL,一般在应用过程中,DEBUG和INFO级别的一般在软件的开发和调试过程中使用,在应用软件上线,用户使用中

用WARNING,ERROR,CRITICAL这三个级别,这样可以减少日志的输出,提高从日志查找定位原因的效率

三、使用logging提供的模块级别输出日志

1.最简单的日志输出

#!/usr/bin/env python
# encoding: utf-8
#@author: 张新礼
import logging

logging.debug('用来调试代码')
logging.info('用来记录节点')
logging.warning('可能将会发生问题')
logging.error('局部有问题')
logging.critical('我要停止运行了')
输入结果:
WARNING:root:可能将会发生问题
ERROR:root:局部有问题
CRITICAL:root:我要停止运行了

从上边的代码可能有一下几个问题

问题1:为什么前两条日志没有被打印出来?

这是因为logging模块提供的日志记录函数默认日志级别是WARNING,因此只有等于和大于WARNING级别的日志记录被输出了,而小于日志记录被丢弃了。

问题2:打印出来的日志信息中各字段表示什么意思?

上边输出的日志字段代表的意思

  日志级别:日志器:日志信息
问题3:为什么这样输出?

因为logging模块提供的日志记录函数所使用的日志器设置的日志格式默认是BASIC_FORMAT,其值为:

 问题4:我想输入特定格式的日志信息该怎么修改?

 其实很简单,在我们调用上面这些日志记录函数之前,手动调用一下basicConfig()方法,把我们想设置的内容以参数的形式传递进去就可以了。

2. logging.basicConfig()函数说明

该方法用于为logging日志系统做一些基本配置,方法定义如下:

logging.basicConfig(**kwargs)

该函数支持的参数如下:

参数名称描述
filename 指定日志输出目标文件的文件名,指定该设置项后日志信心就不会被输出到控制台了
filemode 指定日志文件的打开模式,默认为'a'。需要注意的是,该选项要在filename指定时才有效
format 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
datefmt 指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效
level 指定日志器的日志级别
stream 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常
对与logging模块中定义好的可以用于format格式字符串中字段有哪些

格式

描述

%(levelno)s

打印日志级别的数值

%(levelname)s

打印日志级别名称

%(pathname)s

打印当前执行程序的路径

%(filename)s

打印当前执行程序名称

%(funcName)s

打印日志的当前函数

%(lineno)d

打印日志的当前行号

%(asctime)s

打印日志的时间

%(thread)d

打印线程id

%(threadName)s

打印线程名称

%(process)d

打印进程ID

%(message)s

打印日志信息

3.经过配置的日志输出

先简单配置下日志器的日志级别
#!/usr/bin/env python
# encoding: utf-8
#@author: 张新礼
import logging

logging.basicConfig(level=logging.DEBUG)

logging.debug('用来调试代码')
logging.info('用来记录节点')
logging.warning('可能将会发生问题')
logging.error('局部有问题')
logging.critical('我要停止运行了')

输出结果:

DEBUG:root:用来调试代码
INFO:root:用来记录节点
WARNING:root:可能将会发生问题
ERROR:root:局部有问题
CRITICAL:root:我要停止运行了

所有等级的日志信息都被输出了,说明配置生效了。

在配置日志器日志级别的基础上,在配置下日志输出目标文件和日志格式
#!/usr/bin/env python
# encoding: utf-8
#@author: 张新礼
import logging

logging.basicConfig(level=logging.DEBUG,filename='runlog.log',
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')

logging.debug('用来调试代码')
logging.info('用来记录节点')
logging.warning('可能将会发生问题')
logging.error('局部有问题')
logging.critical('我要停止运行了')

此时会发现控制台中已经没有输出日志内容了,输出结果会在runlog.log的文件里

2022-04-19 17:35:46,504 log01.py[line:9] DEBUG 用来调试代码
2022-04-19 17:35:46,504 log01.py[line:10] INFO 用来记录节点
2022-04-19 17:35:46,504 log01.py[line:11] WARNING 可能将会发生问题
2022-04-19 17:35:46,504 log01.py[line:12] ERROR 局部有问题
2022-04-19 17:35:46,504 log01.py[line:13] CRITICAL 我要停止运行了
特别说明:
  • logging.basicConfig()函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。
  • 日志器(Logger)是有层级关系的,上面调用的logging模块级别的函数所使用的日志器是RootLogger类的实例,其名称为'root',它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。

四、logging模块日志流处理流程

在介绍logging模块的高级用法之前,很有必要对logging模块所包含的重要组件以及其工作流程做个全面、简要的介绍,这有助于我们更好的理解我们所写的代码。

1. logging日志模块四大组件

在介绍logging模块的日志流处理流程之前,我们先来介绍下logging模块的四大组件:

组件名称对应类名功能描述
日志器 Logger 提供了应用程序可一直使用的接口
处理器 Handler 将logger创建的日志记录发送到合适的目的输出
过滤器 Filter 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
格式器 Formatter 决定日志记录的最终输出格式

logging模块就是通过这些组件来完成日志处理的,上面所使用的logging模块级别的函数也是通过这些组件对应的类来实现的。

这些组件之间的关系描述:
  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
  • 不同的处理器(handler)可以将日志输出到不同的位置;
  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。

2. logging日志模块相关类及其常用方法介绍

下面介绍下与logging四大组件相关的类:Logger, Handler, Filter, Formatter。

Logger类

Logger对象有3个任务要做:

  • 1)向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息;
  • 2)基于日志严重等级(默认的过滤设施)或filter对象来决定要对哪些日志进行后续处理;
  • 3)将日志消息传送给所有感兴趣的日志handlers。

Logger对象最常用的方法分为两类:配置方法 和 消息发送方法

最常用的配置方法如下:

方法描述
Logger.setLevel() 设置日志器将会处理的日志消息的最低严重级别
Logger.addHandler() 和 Logger.removeHandler() 为该logger对象添加 和 移除一个handler对象
Logger.addFilter() 和 Logger.removeFilter() 为该logger对象添加 和 移除一个filter对象

关于Logger.setLevel()方法的说明:

内建等级中,级别最低的是DEBUG,级别最高的是CRITICAL。例如setLevel(logging.INFO),此时函数参数为INFO,那么该logger将只会处

理INFO、WARNING、ERROR和CRITICAL级别的日志,而DEBUG级别的消息将会被忽略/丢弃。

Handler类

Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:

  • 1)把所有日志都发送到一个日志文件中;
  • 2)把所有严重级别大于等于error的日志发送到stdout(标准输出);
  • 3)把所有严重级别为critical的日志发送到一个email邮件地址。
    这种场景就需要3个不同的handlers,每个handler复杂发送一个特定严重级别的日志到一个特定的位置。

一个handler中只有非常少数的方法是需要应用开发人员去关心的。对于使用内建handler对象的应用开发人员来说,似乎唯一相关的handler方法就是下面这几个配置方法:

方法描述
Handler.setLevel() 设置handler将会处理的日志消息的最低严重级别
Handler.setFormatter() 为handler设置一个格式器对象
Handler.addFilter() 和 Handler.removeFilter() 为handler添加 和 删除一个过滤器对象

需要说明的是,应用程序代码不应该直接实例化和使用Handler实例。因为Handler是一个基类,它只定义了素有handlers都应该有的接口,同时提供了一些子类可以直接使用或覆盖的默认行为。下面是一些常用的Handler:

Handler描述
logging.StreamHandler 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象。
logging.FileHandler 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长
logging.handlers.RotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按大小切割
logging.hanlders.TimedRotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按时间切割
logging.handlers.HTTPHandler 将日志消息以GET或POST的方式发送给一个HTTP服务器
logging.handlers.SMTPHandler 将日志消息发送给一个指定的email地址
logging.NullHandler 该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免'No handlers could be found for logger XXX'信息的出现。
Formater类

Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个Formatter的子类来完成。

Formatter类的构造方法定义如下:

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

可见,该构造方法接收3个可选参数:

  • fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
  • datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
  • style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
Filter类

Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。该类定义如下:

class logging.Filter(name='')
    filter(record)

比如,一个filter实例化时传递的name参数值为'A.B',那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:'A.B','A.B,C','A.B.C.D','A.B.D',而名称为'A.BB', 'B.A.B'的loggers产生的日志则会被过滤掉。如果name的值为空字符串,则允许所有的日志事件通过过滤。

filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。

说明:

  • 如果有需要,也可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。
  • 我们还可以通过filter做一些统计工作,比如可以计算下被一个特殊的logger或handler所处理的record数量等。

 

3. logging日志流处理流程

简单的日志流处理流程,复杂的自己学

 

五、配置日志的几种方式

我们可以通过以下3种方式来配置logging:

1)使用Python代码显式的创建loggers, handlers和formatters并分别调用它们的配置函数;

2)创建一个日志配置文件,然后使用fileConfig()函数来读取该文件的内容;

3)创建一个包含配置信息的dict,然后把它传递个dictConfig()函数,一般这个方式不常用

需要说明的是,logging.basicConfig()也属于第一种方式,它只是对loggers, handlers和formatters的配置函数进行了封装。另外,第二种配置方式相对于第一种配置方式的优点在于,它将配置信息和代码进行了分离,这一方面降低了日志的维护成本,同时还使得非开发人员也能够去很容易地修改日志配置。

1、使用Python代码实现日志配置

需求

现在有以下几个日志记录的需求:
1)要求将所有级别的所有日志都控制台输出
2)控制台记录所有的日志信息,日志格式为:日期和时间 - 日志器名称 - 文件名 - 日志级别 - 日志信息
3)runlog.log文件中单独记录warning及以上级别的日志信息,日志格式为:日期和时间 - 日志器名称 - 日志级别 - 文件名[:行号] - 日志信息

 
 1 #!/usr/bin/env python
 2 # encoding: utf-8
 3 #@author: 张新礼
 4 import logging
 5 # 创建一个日志器logger并设置其日志级别为DEBUG
 6 logger = logging.getLogger()
 7 logger.setLevel(logging.DEBUG)
 8 
 9 # 创建一个流处理器handler并设置其日志级别为DEBUG
10 handler = logging.StreamHandler()
11 #设置流处理器的级别
12 handler.setLevel(logging.DEBUG)
13 # 处理器设置输出格式
14 handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(filename)s - %(levelname)s - %(message)s"))
15 
16 #创建一个文件处理器并设置日志的级别
17 file_handler=logging.FileHandler("runlog.log")
18 #设置文件处理器的级别
19 file_handler.setLevel(logging.WARNING)
20 #设置文件处理器的输出格式
21 file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(filename)s - [line:%(lineno)d] - %(levelname)s - %(message)s"))
22 
23 # 为日志器logger添加上面创建的处理器handler
24 logger.addHandler(handler)
25 logger.addHandler(file_handler)
26 
27 # 日志采集点
28 logging.debug('用来调试代码')
29 logging.info('用来记录节点')
30 logging.warning('可能将会发生问题')
31 logging.error('局部有问题')
32 logging.critical('我要停止运行了')

2、使用配置文件和fileConfig()函数实现日志配置

配置文件如下

[loggers]
keys=root,infoLogger

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0

[handlers]
keys=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=form01
args=('runlog.log', 'a')

[formatters]
keys=form01,form02

[formatter_form01]
format=%(asctime)s %(name)s  %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

[formatter_form02]
format=%(asctime)s  %(name)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
 1 #!/usr/bin/env python
 2 # encoding: utf-8
 3 #@author: 张新礼
 4 import logging
 5 import  logging.config

 7 logging.config.fileConfig("log.conf")
 8 logger = logging.getLogger()
 9 
10 
11 # 日志采集点
12 logging.debug('用来调试代码')
13 logging.info('用来记录节点')
14 logging.warning('可能将会发生问题')
15 logging.error('局部有问题')
16 logging.critical('我要停止运行了')

 

 

posted on 2019-11-01 20:41  礼哥宝典  阅读(339)  评论(0)    收藏  举报