Python3 logging (日志)
时间:2019-05-13
环境:python3.7.3
官方文档:https://docs.python.org/3/library/logging.config.html#logging-config-dictschetma
1. 相关模块引入
import logging
import logging.config
2. 基础使用
#!/usr/local/bin/python3
# -*- coding:utf-8 -*-
import logging
logging.debug('debug message')
logging.info('info message')
logging.warning('warn message')
logging.error('error message')
logging.critical('critical message')
输出结果:
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message
结论:
*. 默认 打印等级:WARNING(即只有日志级别高于WARNING的日志信息才会输出)
*. 默认 打印格式:[%(levelname)s]:[%(name)s]:[%(message)s]
即:[日志等级]:[Logger实例名称]:[日志消息内容]
实际开发中,需要更多自定义化的配置,在开始自定义配置前,先了解几个核心概念
3. 核心概念
- 1.Logger 记录器:暴露了应用程序代码能直接使用的接口。
- 2.Handler 处理器:将(记录器产生的)日志记录发送至合适的目的地。
- 3.Filter 过滤器:提供了更好的粒度控制,它可以决定输出哪些日志记录。
- 4.Formatter 格式化器:指明了最终输出中日志记录的布局。
3.1 Logger 记录器
Logger是一个树形层级结构,在使用接口debug,info,warn,error,critical之前必须创建Logger实例,即创建一个记录器,如果没有显式的进行创建,则默认创建一个root logger,并应用默认的日志级别(WARN),处理器Handler(StreamHandler,即将日志信息打印输出在标准输出上),和格式化器Formatter(默认的格式即为第一个简单使用程序中输出的格式)。
创建方法: logger = logging.getLogger(logger_name)
创建Logger实例后,可以使用以下方法进行日志级别设置,增加处理器Handler。
logger.setLevel(logging.ERROR) # 设置日志级别为ERROR,即只有日志级别大于等于ERROR的日志才会输出
logger.addHandler(handler_name) # 为Logger实例增加一个处理器
logger.removeHandler(handler_name) # 为Logger实例删除一个处理器
3.2 Handler 处理器
Handler处理器类型有很多种,比较常用的有三个,StreamHandler,FileHandler,NullHandler,详情可以访问 Python logging.handlers
创建StreamHandler之后,可以通过使用以下方法设置日志级别,设置格式化器Formatter,增加或删除过滤器Filter。
ch.setLevel(logging.WARN) # 指定日志级别,低于WARN级别的日志将被忽略
ch.setFormatter(formatter_name) # 设置一个格式化器formatter
ch.addFilter(filter_name) # 增加一个过滤器,可以增加多个
ch.removeFilter(filter_name) # 删除一个过滤器
3.2.1 StreamHandler
- logging模块中的三个handler之一。
- 能够将日志信息输出到sys.stdout, sys.stderr 或者类文件对象(更确切点,就是能够支持write()和flush()方法的对象)。
构造参数:
class logging.StreamHandler(stream=None)
日志信息会输出到指定的stream中,如果stream为空则默认输出到sys.stderr
3.2.2 FileHandler
- logging模块自带的三个handler之一。
- 继承自StreamHandler。将日志信息输出到磁盘文件上。
构造参数:
class logging.FileHandler(filename, mode='a', encoding=None, delay=False)
模式默认为append,delay为true时,文件直到emit方法被执行才会打开。默认情况下,日志文件可以无限增大。
3.2.3 NullHandler
- logging模块自带的三个handler之一。
- 空操作handler, 没有参数。
3.2.4 WatchedFileHandler
- 位于logging.handlers模块中。
- 用于监视文件的状态,如果文件被改变了,那么就关闭当前流,重新打开文件,创建一个新的流。由于newsyslog或者logrotate的使用会导致文件改变。这个handler是专门为linux/unix系统设计的,因为在windows系统下,正在被打开的文件是不会被改变的。
参数和FileHandler相同:
class logging.handlers.WatchedFileHandler(filename, mode='a', encoding=None, delay=False)
3.2.5 RotatingFileHandler
- 位于logging.handlers支持循环日志文件。
构造参数:
class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)
参数maxBytes和backupCount允许日志文件在达到maxBytes时rollover.当文件大小达到或者超过maxBytes时,就会新创建一个日志文件。
上述的这两个参数任一一个为0时,rollover都不会发生。也就是就文件没有maxBytes限制。backupcount是备份数目,也就是最多能有多少个备份。
命名会在日志的base_name后面加上.0-.n的后缀,如example.log.1,example.log.1,…,example.log.10。当前使用的日志文件为base_name.log。
3.2.6 TimedRotatingFileHandler
定时循环日志handler,位于logging.handlers,支持定时生成新日志文件。
构造参数:
class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False)
参数when决定了时间间隔的类型,参数interval决定了多少的时间间隔。如when='D',interval=2,就是指两天的时间间隔,backupCount决定了能留几个日志文件。超过数量就会丢弃掉老的日志文件。
when的参数决定了时间间隔的类型。两者之间的关系如下:
'S' | 秒
'M' | 分
'H' | 时
'D' | 天
'W0'-'W6' | 周一至周日
'midnight' | 每天的凌晨
utc参数表示UTC时间。
3.2.7 其他handler——SocketHandler、DatagramHandler、SysLogHandler、NtEventHandler、SMTPHandler、MemoryHandler、HTTPHandler
这些handler都不怎么常用,所以具体介绍就请参考官方文档 (https://docs.python.org/3.7/library/logging.handlers.html)
3.3 Formatter 格式化器
使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。
创建方法: formatter = logging.Formatter(fmt=None, datefmt=None, style='%')
fmt:消息的格式化字符串,如果不指明fmt,将使用'%(message)s'。
datefmt:日期字符串。如果不指明datefmt,将使用ISO8601日期格式。
style:引用样式,默认'%', 即'%()s'
3.4 Filter 过滤器
Handlers和Loggers可以使用Filters来完成比级别更复杂的过滤。Filter基类只允许特定Logger层次以下的事件。
例如用'A.B'初始化的Filter允许Logger 'A.B', 'A.B.C', 'A.B.C.D', 'A.B.D'等记录的事件,logger'A.BB', 'B.A.B' 等就不行。如果用空字符串来初始化,所有的事件都接受。
创建方法: filter = logging.Filter(name='')
以下是相关概念总结:
熟悉了这些概念之后,有另外一个比较重要的事情必须清楚,即Logger是一个树形层级结构;
Logger可以包含一个或多个Handler和Filter,即Logger与Handler或Fitler是一对多的关系;
一个Logger实例可以新增多个Handler,一个Handler可以新增多个格式化器或多个过滤器,而且日志级别将会继承。
3.5 Logging工作流程
logging模块使用过程
第一次导入logging模块或使用reload函数重新导入logging模块,logging模块中的代码将被执行,这个过程中将产生logging日志系统的默认配置。
-
自定义配置(可选)。logging标准模块支持三种配置方式: dictConfig,fileConfig,listen。
- dictConfig是通过一个字典进行配置Logger,Handler,Filter,Formatter;
- fileConfig则是通过一个文件进行配置
- listen则监听一个网络端口,通过接收网络数据来进行配置。
当然,除了以上集体化配置外,也可以直接调用Logger,Handler等对象中的方法在代码中来显式配置。
使用logging模块的全局作用域中的getLogger函数来得到一个Logger对象实例(其参数即是一个字符串,表示Logger对象实例的名字,即通过该名字来得到相应的Logger对象实例)。
使用Logger对象中的debug,info,error,warn,critical等方法记录日志信息。
logging模块处理流程
- 判断日志的等级是否大于Logger对象的等级,如果大于,则往下执行,否则,流程结束。
- 产生日志。第一步,判断是否有异常,如果有,则添加异常信息。第二步,处理日志记录方法(如debug,info等)中的占位符,即一般的字符串格式化处理。
- 使用注册到Logger对象中的Filters进行过滤。如果有多个过滤器,则依次过滤;只要有一个过滤器返回假,则过滤结束,且该日志信息将丢弃,不再处理,而处理流程也至此结束。否则,处理流程往下执行。
- 在当前Logger对象中查找Handlers,如果找不到任何Handler,则往上到该Logger对象的父Logger中查找;如果找到一个或多个Handler,则依次用Handler来处理日志信息。但在每个Handler处理日志信息过程中,会首先判断日志信息的等级是否大于该Handler的等级,如果大于,则往下执行(由Logger对象进入Handler对象中),否则,处理流程结束。
- 执行Handler对象中的filter方法,该方法会依次执行注册到该Handler对象中的Filter。如果有一个Filter判断该日志信息为假,则此后的所有Filter都不再执行,而直接将该日志信息丢弃,处理流程结束。
- 使用Formatter类格式化最终的输出结果。 注:Formatter同上述第2步的字符串格式化不同,它会添加额外的信息,比如日志产生的时间,产生日志的源代码所在的源文件的路径等等。
- 真正地输出日志信息(到网络,文件,终端,邮件等)。至于输出到哪个目的地,由Handler的种类来决定。
4. 自定义配置
4.1 格式配置
4.1.1 format 格式中的变量
格式 | 描述 |
---|---|
%(asctime)s | 打印日志的时间 |
%(process)d | 打印进程ID |
%(thread)d | 打印线程id |
%(threadName)s | 打印线程名称 |
%(levelname)s | 打印日志级别名称 |
%(levelno)s | 打印日志级别的数值 |
%(pathname)s | 打印当前执行程序的路径 |
%(filename)s | 打印当前执行程序名称 |
%(funcName)s | 打印日志的当前函数 |
%(lineno)d | 打印日志的当前行号 |
%(message)s | 打印日志信息 |
格式配置:
完整输出: %(asctime)s [%(process)d] [%(threadName)s(%(thread)d)] [%(name)s] [%(levelname)s(%(levelno)s)] %(pathname)s.%(filename)s[line:%(lineno)d, funcName:%(funcName)s] %(message)s'
个人格式(延续Java logback习惯): '%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(filename)s[line:%(lineno)d] %(message)s'
4.1.2 datefmt
参考: https://docs.python.org/3.7/library/time.html?highlight=strftime#time.strftime
格式 | 描述 |
---|---|
%a | 本地化的缩写星期中每日的名称 |
%A | 本地化的星期中每日的完整名称 |
%b | 本地化的月缩写名称 |
%B | 本地化的月完整名称 |
%c | 本地化的适当日期和时间表示 |
%d | 十进制数 [01,31] 表示的月中日 |
%H | 十进制数 [00,23] 表示的小时(24小时制) |
%I | 十进制数 [01,12] 表示的小时(12小时制) |
%j | 十进制数 [001,366] 表示的年中日 |
%m | 十进制数 [01,12] 表示的月 |
%M | 十进制数 [00,59] 表示的分钟 |
%p | 本地化的 AM 或 PM |
%S | 十进制数 [00,61] 表示的秒 |
%U | 十进制数 [00,53] 表示的一年中的周数(星期日作为一周的第一天)作为。在第一个星期日之前的新年中的所有日子都被认为是在第0周 |
%w | 十进制数 [0(星期日),6] 表示的周中日 |
%W | 十进制数 [00,53] 表示的一年中的周数(星期一作为一周的第一天)作为。在第一个星期一之前的新年中的所有日子被认为是在第0周 |
%x | 本地化的适当日期表示 |
%X | 本地化的适当时间表示 |
%y | 十进制数 [00,99] 表示的没有世纪的年份 |
%Y | 十进制数表示的带世纪的年份 |
%z | 时区偏移以格式 +HHMM 或 -HHMM 形式的 UTC/GMT 的正或负时差指示,其中H表示十进制小时数字,M表示小数分钟数字 [-23:59, +23:59] |
%Z | 时区名称(如果不存在时区,则不包含字符) |
%% | 字面的 '%' 字符 |
4.2 第一种:显式调用操作
显式创建 Logger记录器、Handler处理器 和 Formatter格式化器,并进行相关设置
import logging.config
# create logger
logger_name = "example"
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
# create file handler
log_path = "./loggingDemo-direct.log"
fh = logging.FileHandler(log_path)
fh.setLevel(logging.WARNING)
# create formatter
fmt = '%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(filename)s[line:%(lineno)d] %(message)s'
datefmt = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter(fmt, datefmt)
# add handler and formatter to logger
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.debug('log debug')
logger.info('log info')
logger.warning('log warning')
logger.error('log error')
logger.critical('log critical')
4.3 第二种:logging.basicConfig() 配置
key | 描述 |
---|---|
filename | 指定使用指定的文件名,而不是StreamHandler创建FileHandler。 |
filemode | 如果指定了文件名,请在此模式下打开文件。 默认为'a'。 |
format | 格式化字符串 |
datefmt | 格式化日期/时间格式 |
style | 引用样式,默认'%', 即'%()s' |
level | logger 的 等级 |
stream | 使用指定的流初始化StreamHandler。 注:此参数与filename不兼容 - 如果两者都存在,则引发ValueError。 |
handlers | 如果指定,则应该是已创建的处理程序的可迭代,以添加到根记录器。 任何没有格式化程序集的处理程序都将被分配在此函数中创建的默认格式化程序。 请注意,此参数与filename或stream不兼容 - 如果两者都存在,则引发ValueError。 |
实例:
import logging.config
logging.basicConfig(
filename='loggingDemo-basicConfig.log',
level=logging.DEBUG,
format='%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(filename)s[line:%(lineno)d] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logging.debug('log debug')
logging.info('log info')
logging.warning('log warning')
logging.error('log error')
logging.critical('log critical')
4.4 第三种 logging.config.fileConfig 读取配置文件
配置文件logging.conf如下:
[loggers]
keys=root, fileLogger, rotatingFileLogger
[handlers]
keys=consoleHandler, fileHandler, rotatingFileHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_fileLogger]
level=DEBUG
# 该logger中配置的handler
handlers=fileHandler
# logger 的名称
qualname=fileLogger
propagate=0
[logger_rotatingFileLogger]
level=DEBUG
# 这样配置,rotatingFileLogger中就同时配置了consoleHandler,rotatingFileHandler
# consoleHandler 负责将日志输出到控制台
# rotatingFileHandler 负责将日志输出保存到文件中
handlers=consoleHandler,rotatingFileHandler
qualname=rotatingFileLogger
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('./loggingDemo-file.log', 'a')
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=WARNING
formatter=simpleFormatter
args=('./loggingDemo-rotating.log', 'a', 1*1024*1024, 5)
[formatter_simpleFormatter]
format=%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(filename)s[line:%(lineno)d] %(message)s
datefmt=%Y-%m-%d %H:%M:%S
实例:
import logging.config
logging.config.fileConfig('./logging.conf')
log = logging.getLogger(name='rotatingFileLogger')
log.debug('log debug')
log.info('log info')
log.warning('log warning')
log.error('log error')
log.critical('log critical')
4.5 第四种 logging.config.dictConfig 读取配置信息
import logging.config
dictConfig = {
'version': 1,
'disable_existing_loggers': True,
'incremental': False,
'formatters': {
'master_format': {
'class': 'logging.Formatter',
'format': '%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(filename)s[line:%(lineno)d] %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
}
},
'filters': {
'filter_by_name': {
'class': 'logging.Filter',
'name': 'fileLogger'
},
# 仅 INFO 能输出
'filter_single_level_pass': {
'class': 'logging.StreamHandler',
'pass_level': logging.INFO
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': logging.DEBUG,
'formatter': 'master_format',
},
'fileHandler': {
'class': 'logging.FileHandler',
'filename': 'logfile.log',
'level': logging.INFO,
'formatter': 'master_format',
'filters': ['filter_by_name', ],
},
'rotatingFileHandler': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'rotatingLogfile.log',
'level': logging.DEBUG,
'formatter': 'master_format',
'maxBytes': 256,
'backupCount': 2
},
},
'loggers': {
'root': {
'handlers': ['console', ],
'level': 'INFO',
'propagate': False
},
'fileLogger': {
'handlers': ['console', 'fileHandler'],
'filters': ['filter_by_name', ],
'level': 'DEBUG'
},
'rotatingFileLogger': {
'handlers': ['console', 'rotatingFileHandler'],
'level': 'INFO'
}
}
}
logging.config.dictConfig(dictConfig)
log = logging.getLogger(name='fileLogger')
log.debug('log debug')
log.info('log info')
log.warning('log warning')
log.error('log error')
log.critical('log critical')
log2 = logging.getLogger(name='rotatingFileLogger')
log2.debug('log debug')
log2.info('log info')
log2.warning('log warning')
log2.error('log error')
log2.critical('log critical')
4.6 第五种 listen() 进行网络配置。
更多详细内容参考logging.config日志配置
参考资料
- [ 作者:好吃的野菜] https://www.jianshu.com/p/feb86c06c4f4