文章时效性提示
这是一篇发布于 701 天前的文章,部分信息可能已发生改变,请注意甄别。
开发一个程序或者软件使用日志记录是非常有用的,对于开发者DEBUG,维护以及有经验的程序使用者都可以根据日志判断软件出了什么问题以及能做什么.这里主要介绍使用Python的原生模块logging写日志.
日志概念
日志是一种可以追踪某些软件运行时所发生事件的方法。软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。一个事件可以用一个可包含可选变量数据的消息来描述。
此外,事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)。
在部署项目时,不可能直接将所有的信息都输出到控制台中,我们可以将这些信息记录到日志文件中,这样不仅方便我们查看程序运行时的情况,也可以在项目出现故障时根据程序运行时产生的日志快速定位问题出现的位置。
日志等级,在python的logging模块中有以下几个等级以及等级的值
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0
基本使用
1 | import logging |
使用basicConfig
进行基本配置,
可以设置filename
作为日志信息的输入文件.
可以创造多个 Logger 对象,但是真正输出日志的是root Logger 对象。每个 Logger 对象都可以设置一个名字,如果设置logger = logging.getLogger(__name__)
,name 是 Python 中的一个特殊内置变量,他代表当前模块的名称(默认为 main)。则 Logger 对象的 name 为建议使用使用以点号作为分隔符的命名空间等级制度。
Logging组件
组件名称 | 类名 | 描述 |
---|---|---|
日志器 | Logger | 提供程序一直使用的接口 |
处理器 | Handler | 将日志记录发送到合适的目的输出 |
过滤器 | Filer | 决定输出哪条日志记录 |
格式器 | Formatter | 决定日志记录的最终输出格式 |
如果直接想要简单使用,可以直接使用logging
对象,
1 | import logging |
对于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 | logger.debug("DEBUG") |
常用的就是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 | handler = logging.StreamHandler() |
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 | formatter = logging.Formatter('Hello %(message)s') |
Filter类
1 | class NoParsingFilter(logging.Filter): |
可以使用类或者函数作为Filter
1 | logger.addFilter(NoParsingFilter()) |
同时也可以在类中的初始化函数设置类的属性
1 | class Filter(object): |
注意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
常见问题
- 重复写日志问题
用Python的logging模块记录日志时,可能会遇到重复记录日志的问题,第一条记录写一次,第二条记录写两次,第三条记录写三次
原因::第二次调用log的时候,根据getLogger(name)里的name获取同一个logger,而这个logger里已经有了第一次你添加的handler,第二次调用又添加了一个handler,所以,这个logger里有了两个同样的handler,以此类推,调用几次就会有几个handler。
解决办法:1. 最后移除handler 2.做判断,若logger已有handler,则不再添加handler
代码如下
1 | #解决方案1,添加removeHandler语句,每次用完之后移除Handler |
或者
1 | if not logger.handlers: |
logging配置文件
可以使用logging.conf.fileconf
进行配置logging
1 | import logging.config |
下面是一个logging.conf
文件demo
1 | 文件配置 |