python中的logging模块

开发一个程序或者软件使用日志记录是非常有用的,对于开发者DEBUG,维护以及有经验的程序使用者都可以根据日志判断软件出了什么问题以及能做什么.这里主要介绍使用Python的原生模块logging写日志.

日志概念

日志是一种可以追踪某些软件运行时所发生事件的方法。软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。一个事件可以用一个可包含可选变量数据的消息来描述。

此外,事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)。

在部署项目时,不可能直接将所有的信息都输出到控制台中,我们可以将这些信息记录到日志文件中,这样不仅方便我们查看程序运行时的情况,也可以在项目出现故障时根据程序运行时产生的日志快速定位问题出现的位置。

日志等级,在python的logging模块中有以下几个等级以及等级的值

CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

基本使用

1
2
3
4
import logging

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

使用basicConfig进行基本配置,

image-20230502221157465

可以设置filename作为日志信息的输入文件.

可以创造多个 Logger 对象,但是真正输出日志的是root Logger 对象。每个 Logger 对象都可以设置一个名字,如果设置logger = logging.getLogger(__name__)name 是 Python 中的一个特殊内置变量,他代表当前模块的名称(默认为 main)。则 Logger 对象的 name 为建议使用使用以点号作为分隔符的命名空间等级制度。

Logging组件

组件名称类名描述
日志器Logger提供程序一直使用的接口
处理器Handler将日志记录发送到合适的目的输出
过滤器Filer决定输出哪条日志记录
格式器Formatter决定日志记录的最终输出格式

如果直接想要简单使用,可以直接使用logging对象,

1
2
3
4
5
6
7
8
9
10
import logging
logging.basicConfig(level=logging.DEBUG,
format="%(asctime)s %(name)s %(levelname)s %(message)s",
datefmt = '%Y-%m-%d %H:%M:%S %a'
)
logging.debug("msg1")
logging.info("msg2")
logging.warning("msg3")
logging.error("msg4")
logging.critical("msg5")

对于format的值

字段/属性名称使用格式描述
asctime%(asctime)s将日志的时间构造成可读的形式,默认情况下是‘2016-02-08 12:00:00,123’精确到毫秒
name%(name)s所使用的日志器名称,默认是’root’,因为默认使用的是 rootLogger
filename%(filename)s调用日志输出函数的模块的文件名; pathname的文件名部分,包含文件后缀
funcName%(funcName)s由哪个function发出的log, 调用日志输出函数的函数名
levelname%(levelname)s日志的最终等级(被filter修改后的)
message%(message)s日志信息, 日志记录的文本内容
lineno%(lineno)d当前日志的行号, 调用日志输出函数的语句所在的代码行
levelno%(levelno)s该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
pathname%(pathname)s完整路径 ,调用日志输出函数的模块的完整路径名,可能没有
process%(process)s当前进程, 进程ID。可能没有
processName%(processName)s进程名称,Python 3.1新增
thread%(thread)s当前线程, 线程ID。可能没有
threadName%(thread)s线程名称
module%(module)s调用日志输出函数的模块名, filename的名称部分,不包含后缀即不包含文件后缀的文件名
created%(created)f当前时间,用UNIX标准的表示时间的浮点数表示; 日志事件发生的时间—时间戳,就是当时调用time.time()函数返回的值
relativeCreated%(relativeCreated)d输出日志信息时的,自Logger创建以 来的毫秒数; 日志事件发生的时间相对于logging模块加载时间的相对毫秒数
msecs%(msecs)d日志事件发生事件的毫秒部分。logging.basicConfig()中用了参数datefmt,将会去掉asctime中产生的毫秒部分,可以用这个加上

对于datefmt字段,对应的是format字段的%(asctime)s,用于修改时间显示格式.

level字段用于显示的最低等级

总结来说,简单实用就是配置logging.basicConfig()(用默认日志格式(Formatter)为日志系统建立一个默认的流处理器(StreamHandler),设置基础配置(如日志级别等)并加到root logger(根Logger)中)这几个logging模块级别的函数。

进阶使用

进阶使用跟上述的模块四大组件有关,

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

Logger类

Logger对象有3个任务要做:

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

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

一般通过logging.getLogger(__name__)得到logger对象,然后通过logger.setLevel设置logger过滤的等级.

通过addHandler和addFilter添加处理器和过滤器.

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

使用

1
2
3
4
5
6
logger.debug("DEBUG")
logger.info("INFO")
logger.warning("WARNING")
logger.error("ERROR")
logger.critical("CRITICAL")
logger.log(15,"hello")

常用的就是info,debug,warning,error几种日志等级,也可以通过logger.log设置自己的日志等级和消息.

Handler类

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

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

Handler一般使用StreamHandler和FileHandler用于输入到Stream和磁盘文件.

1
2
handler = logging.StreamHandler()
logger.addHandler(handler)

handler也可以设置level,过滤通过了logger的日志,此外handler也可以设置Filter用于更细致的过滤以及设置Formatter用于输出格式.

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’信息的出现。

Formatter类

常用的两个参数是

  • fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
  • datefmt:指定日期格式字符串,如果不指定该参数则默认使用”%Y-%m-%d %H:%M:%S”
1
2
formatter = logging.Formatter('Hello %(message)s')
handler.setFormatter(formatter)

image-20230503222936462

Filter类

1
2
3
4
5
6
7
8
9
10
11
12
13
class NoParsingFilter(logging.Filter):
def __init__(self):
super().__init__()
def filter(self, record):
print(record.getMessage())
return not record.getMessage().startswith('parsing')


def filter_grammar_messages(record):
print(record.funcName,"---------------")
if record.funcName == 'load_grammar':
return False
return True

可以使用类或者函数作为Filter

1
2
logger.addFilter(NoParsingFilter())
logger.addFilter(filter_grammar_messages)

同时也可以在类中的初始化函数设置类的属性

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
class Filter(object):
"""
Filter instances are used to perform arbitrary filtering of LogRecords.

Loggers and Handlers can optionally use Filter instances to filter
records as desired. The base filter class only allows events which are
below a certain point in the logger hierarchy. For example, a filter
initialized with "A.B" will allow events logged by loggers "A.B",
"A.B.C", "A.B.C.D", "A.B.D" etc. but not "A.BB", "B.A.B" etc. If
initialized with the empty string, all events are passed.
"""
def __init__(self, name=''):
"""
Initialize a filter.

Initialize with the name of the logger which, together with its
children, will have its events allowed through the filter. If no
name is specified, allow every event.
"""
self.name = name
self.nlen = len(name)

def filter(self, record):
"""
Determine if the specified record is to be logged.

Is the specified record to be logged? Returns 0 for no, nonzero for
yes. If deemed appropriate, the record may be modified in-place.
"""
if self.nlen == 0:
return True
elif self.name == record.name:
return True
elif record.name.find(self.name, 0, self.nlen) != 0:
return False
return (record.name[self.nlen] == ".")

注意Filter也有默认行为,过滤行为设计到初始化的Filer,这里就不细说了.

日志流处理简要流程

1、创建一个logger

2、设置下logger的日志的等级

3、创建合适的Handler(FileHandler要有路径)

4、设置下每个Handler的日志等级

5、创建下日志的格式

6、向Handler中添加上面创建的格式

7、将上面创建的Handler添加到logger中

8、打印输出logger.debug\logger.info\logger.warning\logger.error\logger.critical

常见问题

  1. 重复写日志问题

用Python的logging模块记录日志时,可能会遇到重复记录日志的问题,第一条记录写一次,第二条记录写两次,第三条记录写三次

原因::第二次调用log的时候,根据getLogger(name)里的name获取同一个logger,而这个logger里已经有了第一次你添加的handler,第二次调用又添加了一个handler,所以,这个logger里有了两个同样的handler,以此类推,调用几次就会有几个handler。

解决办法:1. 最后移除handler 2.做判断,若logger已有handler,则不再添加handler

代码如下

1
2
3
   #解决方案1,添加removeHandler语句,每次用完之后移除Handler
logger.removeHandler(fh)
logger.removeHandler(ch)

或者

1
2
3
4
5
6
7
8
9
10
if not logger.handlers:
#创建handler
fh = logging.FileHandler("test.log",encoding="utf-8")
ch = logging.StreamHandler()

#设置输出日志格式
formatter = logging.Formatter(
fmt="%(asctime)s %(name)s %(filename)s %(message)s",
datefmt="%Y/%m/%d %X"
)

logging配置文件

可以使用logging.conf.fileconf进行配置logging

1
2
import logging.config
logging.config.fileConfig('logging.conf')

下面是一个logging.conf文件demo

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
文件配置
配置文件logging.conf如下:
[loggers]
keys=root,example01

[logger_root]
level=DEBUG
handlers=hand01,hand02

[logger_example01]
handlers=hand01,hand02
qualname=example01
propagate=0

[handlers]
keys=hand01,hand02

[handler_hand01]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stderr,)

[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form01
args=('log.log', 'a')

[formatters]
keys=form01,form02

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

参考资料

  1. 最棒总结!Python日志库 logging 使用指南来了 - 知乎 (zhihu.com)
  2. Python3 日志(内置logging模块) - 天马行宇 - 博客园 (cnblogs.com)
  3. python logging模块使用教程 - 简书 (jianshu.com)
  4. Python之日志处理(logging模块) - 云游道士 - 博客园 (cnblogs.com)
-------------本文结束感谢您的阅读-------------
感谢阅读.

欢迎关注我的其它发布渠道