把我自己理解的概念用通俗易懂的话讲出来大概就是
查看日志是开发人员日常获取信息、排查异常、发现问题的最好途径,日志记录中通常会标记有异常产生的原因、发生时间、具体错误行数等信息,这极大的节省了我们的排查时间,无形中提高了编码效率。
下表是日志按照级别分类,指的是 Debug、Info、WARNING、ERROR 、CRITICAL
等严重等级进行划分。
级别 | 数值 | 说明 |
---|---|---|
CRITICAL | 50 | 十分严重的问题导致程序已经不能运行 |
ERROR | 40 | 有错误导致程序的某些功能不能按照预期工作 |
WARNING | 30 | 可以是一些意料之外的问题,也可以是一些预期将要发生的问题的警告 |
INFO | 20 | 表明程序正常运行 |
DEBUG | 10 | 比较详细的信息,一般在调试问题的时候使用 |
NOTSET | 0 | 没有日志级别,任何信息都会输出 |
上述级别分类是在日志系统里通用的。在 python 中对应的模块函数分别为 debug()、info()、warning()、error()、critical()
Python 中日志的默认等级是 WARNING
, DEBUG
和 INFO
级别的日志将不会得到显示,在 logging 中更改设置。
先来简单使用下 python 的 logging 模块输出日志的功能
import logging logging.debug('this is a debug msg') logging.info('this is a info msg') logging.warning('this is a warning msg') logging.error('this is a error msg')
输出如下
WARNING:root:this is a warning msg ERROR:root:this is a error msg
这样的输出证实了上面所说
“Python 中日志的默认等级是
WARNING
,DEBUG
和INFO
级别的日志将不会得到显示”
我们来修改下 Python 默认的日志等级为 DEBUG
import logging logging.basicConfig(level=logging.DEBUG) logging.debug('this is a debug msg') logging.info('this is a info msg') logging.warning('this is a warning msg') logging.error('this is a error msg')
再来看看输出结果,正如我们所预期的那样
DEBUG:root:this is a debug msg INFO:root:this is a info msg WARNING:root:this is a warning msg ERROR:root:this is a error msg
上面的代码只是将我们想要的日志信息输出到了控制台,如果我们要记录日志到文件中,就要用到强大的logging
模块级别的函数,或者是 logging
的四大组件了。
上面设置日志级别的 logging.DEBUG
就是所说的级别函数,而四大组件分别是 loggers、handlers、filters、formatters
,从名字上也不难看出这些组件的作用
组件名称 | logging中对应的类 | 功能 |
---|---|---|
日志器 | Logger | 提供日志系统使用的接口 |
处理器 | Handler | 将 logger 收集到的日志发送到指定的地方 |
过滤器 | Filter | 在 logger 收集日志的时候提供过滤规则 |
格式器 | Formatter | 设定日志的输出格式 |
工作原理
日志器(logger)是入口,真正工作的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。
Logger
的三个功能点
logging.debug
Logger
对象常用方法如下
方法 | 功能描述 |
---|---|
Logger.setLevel() | 设置日志处理的最低严重级别 |
Logger.addHandler() | 将 handler 对象添加到 logger 对象中 |
Logger.removeHandler() | 将 hanlder 对象从 logger 对象中移除 |
Logger.addFilter() | 将 filter 对象添加到 logger 对象中 |
Logger.removeFilter() | 将 filter 对象从 logger 对象中移除 |
获取 logger 对象有两种方式,我们通常使用第二种方式
logger = Logger()
logging.getLogger()
方法。 logging.getLogger()
方法有一个可选参数 name
,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为 ‘root’。若以相同的 name 参数值多次调用 getLogger()
方法,将会返回指向同一个 logger 对象的引用。对于 logger 层级的补充
.
分割的层级结构,每个 .
后面的 logger 都是 .
前面的 logger 的 child。例如,有一个名称为 A
的 logger,其它 logger 名称分别为 A.B, A.B.C
都是 A
的后代。propagate
属性设置为False来关闭这种传递机制。Hander 常用的方法也不多
方法 | 功能描述 |
---|---|
Handler.setLevel() | 设置日志处理的最低严重级别 |
Handler.setFormatter() | 为 handler 对象设置一个格式器对象 |
Handler.addFilter() | 将 filter 对象添加到 handler 对象中 |
Handler.removeFilter() | 将 filter 对象从 handler 对象中移除 |
但是和 Logger 类似,我们在获取 handler 对象的时候最好不要直接实例化 Handler 这个基类。logging 模块已经为我们定义好了一些常用的 handler
logging.StreamHandler
将日志消息发送到 Stream, 如 std.out, std.err
logging.FileHandler
将日志消息发送到文件中,默认是 a
模式追加写入logging.handlers.SMTPHandler
将日志消息发送到指定邮箱logging.handlers.HTTPHandler
将日志消息以 GET 或是 POST 的方式发送到一个 HTTP服务器logging.handlers.RotatingFileHandler
日志文件支持按照大小切割并发送到文件logging.handlers.TimedRotatingFileHandler
日志文件支持按照时间切割并发送到文件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
:可取值为 %, {, $
,如果不指定该参数则默认使用 “%”fmt
中允许使用的变量可以参考下表。
**%(name)s**
Logger的名字**%(levelno)s**
数字形式的日志级别**%(levelname)s**
文本形式的日志级别**%(pathname)s**
调用日志输出函数的模块的完整路径名,可能没有**%(filename)s**
调用日志输出函数的模块的文件名**%(module)s**
调用日志输出函数的模块名**%(funcName)s**
调用日志输出函数的函数名**%(lineno)d**
调用日志输出函数的语句所在的代码行**%(created)f**
当前时间,用UNIX标准的表示时间的浮点数表示**%(relativeCreated)d**
输出日志信息时的,自Logger创建以来的毫秒数|**%(asctime)s**
字符串形式的当前时间。默认格式是 “yyyy-mm-dd HH:MM:SS,SSS”**%(thread)d**
线程ID**%(threadName)s**
线程名**%(process)d**
进程ID**%(message)s**
用户输出的消息Filter 可以被 Handler 和 Logger 用来做比 level 更细粒度的、更复杂的过滤功能。Filter 是一个过滤器基类,它只允许某个 logger 层级下的日志事件通过过滤。
比如,一个 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 的值为空字符串,则允许所有的日志事件通过过滤。
import logging import os import sys BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_PATH) def my_logger(log_name='NANCY_LOG', log_file=f'{BASE_PATH}/log_demo/log/nancy.log', level=logging.DEBUG): # 创建logger对象 logger = logging.getLogger(log_name) logger.setLevel(level) # 添加等级 # 创建控制台 console handler ch = logging.StreamHandler() ch.setLevel(level) # 创建文件 handler fh = logging.FileHandler(log_file) fh.setLevel(level) # 创建 formatter formatter = logging.Formatter('%(asctime)s ' '%(filename)s ' '[line:%(lineno)s] ' '%(name)s ' '%(levelname)s: ' '%(message)s') # 添加日志格式 formatter # ch.setFormatter(formatter) fh.setFormatter(formatter) # 把ch fh 添加到 logger中 logger.addHandler(ch) logger.addHandler(fh) return logger def main(): # 测试 logger = my_logger(log_name='nancy test', level=logging.DEBUG) logger.info('test logging info'.center(30, '*')) logger.debug('test logging debug'.center(30, '*')) logger.error('test logging error'.center(30, '*')) logger.warning('test logging warning'.center(30, '*')) logger.critical('test logging critical'.center(30, '*')) if __name__ == '__main__': main()