logging模块是python中自带的日志处理模块,可用于记录程序异常的位置、时间和具体错误信息等,从而方便开发人员检测程序运行过程和捕获、分析程序异常。
按照输出类型来分,logging可选择控制台直接输出日志信息,也可选择将日志信息写入日志文件。
按照日志级别来分,logging中的日志等级从低到高依次为:
日志级别 | 数值 | 说明 |
NOTSET | 0 | 不做设置,自动集成父级Logger的等级。 |
DEBUG | 10 | 详细信息,一般只在调试问题时使用。 |
INFO | 20 | 证明事情按预期工作。 |
WARNING | 30 | 某些没有预料到的事件的提示,或者在将来可能会出现的问题提示。 |
ERROR | 40 | 由于更严重的问题,软件已经不能执行一些功能。 |
CRITIAL | 50 | 严重错误,表明软件已不能继续运行。 |
利用logging进行日志输出配置主要有四种方式:
方式一:logging模块级函数
方式二:实例化Logger类
方式三:通过fileConfig读入config文件
方式四:通过dictConfig加载json、yaml等配置文件
下文将分别进行介绍。
一、logging模块级函数
利用logging模块级函数进行配置的方式非常简单:
第一步:利用logging.basicConfig
进行logging参数的设置
import logging
logging.basicConfig(**kwargs)
"""
参数包括:
filename:日志输出文件的文件名
filemode:写入文件时的模式,默认为追加模式'a'
format: 输出文本信息的格式
datefmt: 输出日期信息的格式
style:文本信息的填充位符号,默认%
level:日志等级,包括'DEBUG'、'INFO'、'WARNING'、'ERROR'、'CRITICAL',默认为'WARNING'级别
stream:文件流,注意该参数和filename参数是不相容的,若设置了filename,则忽略该参数
"""
第二步:利用函数级的日志输出方法进行日志的输出
import logging
logging.debug("调试日志") # DEBUG级别
logging.info('消息日志') # INFO级别
logging.warning("警告日志") # WARNING级别
logging.error('错误日志') # ERROR级别
logging.critical('致命错误日志') # CRITICAL级别
1.1 终端输出日志
import logging
logging.basicConfig(level='DEBUG') # DEBUG级别
logging.debug("调试日志")
1.2 文件输出日志
如果在logging.basicConfig
中直接设置filename
参数,虽然其随着对应的文件句柄handler关闭而关闭,但因为编码问题无法直接写入中文;此时必须通过设置stream
参数。但这种方法的缺点也非常明显:文件流必须一直打开!
import logging
f = open('logging.log', encoding='utf-8', mode='a') # 配置文件流
logging.basicConfig(level='DEBUG', stream=f) # 设置stream参数
logging.debug("调试日志") # 写入logging.log文件
二、实例化Logger类
在介绍通过实例化Logger类进行日志文件配置前,先介绍logging模块中的四大组件类:
2.1 Logger类
日志器,提供了一个可一直使用的用于配置和记录的日志对象,可视为整个日志记录过程的主入口。
2.1.1 Logger类常见的方法
Logger类常见的方法如下:
import logging
# 1. 定义Logger实例化对象
logger1 = logging.Logger()
logger = logging.getLogger(name='A') # 更常用,默认name为root
# 2. 设置消息等级
logger.setLevel() # 默认Warning
# 3. 添加/移除处理器handler
logger.addHandler()
logger.removeHandler()
# 4. 添加/移除过滤器Filter
logger.addFilter()
logger.removeFilter()
# 5. 配置完后,创建日志记录
logger.debug() # 此外还有.info(),.warning(),.error(),.critical()
logger.exception() # 记录异常记录,效果类似于logger.error(),具体使用见第五部分
logger.log() # 必须显示定义明确的level来生成log
2.1.2 Logger对象的继承和反馈机制
Logger对象有两个非常重要的内部机制,继承和反馈。
(1) 继承机制
通过logging.getLogger
生成的Logger对象的默认name
为root
,这是所有Logger对象的最上一级父类。
而在为logging.getLogger
设置name
时,其命名应遵循以.
分隔的分级原则。如分别定义name
为A
、A.B
、A.C
的Logger对象,则A
是A.B
和A.C
的上一级Logger。
Logger对象会按照命名方式,在不重新定义其level
的情况下,会继承上一级父Logger的消息等级,并一直上溯到root
级Logger(其默认level为Waring)。
(2) 反馈机制
下一级Logger的消息会自动反馈给上一级Logger,并根据上一级Logger的level
做出相应的输出,这种反馈机制会逐层作用一直到root
级Logger。
关闭这种反馈机制的方法为设置Logger对象的propagate
属性为0(默认值为1)。
logger.propagate = 0
2.2 Handler类
处理器,将日志消息分发到的各类处理对象,可视为日志记录的核心处理单元。
2.2.1 Handler类常见的方法
import logging
# 1. 定义handler
handler = logging.StreamHandler()
# 2. 设置消息等级
handler.setLevel() # 默认Warning
# 3. 设置格式器
handler.setFormatter()
# 4. 添加/移除过滤器Filter
handler.addFilter()
handler.removeFilter()
2.2.2 常见的Handler类
Handler | 说明 |
StreamHandler | 流文件,可将日志消息进行屏幕标准输出或文件输出。 |
FileHandler | 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长。 |
RotatingFileHandler | 将日志发送到磁盘文件,支持日志文件按大小切分。 |
TimedRotatingFileHandler | 将日志发送到磁盘文件,支持日志文件按时间切分。 |
HTTPHandler | 将日志消息以GET或POST的方式发送给HTTP服务器。 |
SMTPHandler | 将日志消息发送给指定的email地址 |
2.3 Filter类
过滤器,可被Logger对象或Handler使用,用于对日志的输出的等级或内容进行更颗粒度的控制,可视为日志记录的前置过滤条件。
Filter类的主要工作原理为:通过设置日志消息record
需要满足的条件,来对日记进行过滤操作。
该类的定义方式非常简单:
class logging.Filter(name='')
filter(record)
其中name=A
参数表示只有名字为A
的Logger,以及其子Logger(如A.B
,A.B.C
)可以通过该Filter;而默认值name=''
表示所有日志信息均可通过。而实际使用中,过滤的原则有很多种,更细致的定义方式需要通过设置其中的record
对象。
2.3.1 LogRecord类
日志消息record
其实是logging模块中LogRecord
类,其常用的属性包括:
name:所属Logger的name
level:消息等级
pathname:文件路径
lineno: 代码所处行数
msg:消息内容
exc_info:执行的反馈信息
# 此外,还可以自定义某个关注的key,具体见下面的例子
2.3.2 自定义Filter类的一个案例(捕获自定义的key)
import logging
import sys
class ContextFilter(logging.Filter):
"""
这是一个控制日志记录的过滤器。
仅记录Task=logToConsole的消息,也可以通过record自带的name、line等进行相关设置。
"""
def filter(self, record):
try:
filter_key = record.TASK
except AttributeError:
return False
if filter_key == "logToConsole":
return True
else:
return False
logger = logging.getLogger(__name__)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.WARN)
console_fmt = '%(asctime)-15s [%(TASK)s] %(message)s'
console_formatter = logging.Formatter(console_fmt)
console_handler.setFormatter(console_formatter)
console_filter = ContextFilter()
console_handler.addFilter(console_filter)
logger.addHandler(console_handler)
filter_dict = {'TASK': 'logToConsole'}
# 记录日志
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message1', extra=filter_dict)
logger.error('error message2')
2.4 Formatter类
格式器,可作为模块级函数直接使用,或被Handler使用,用于定义日志输出的文本或日期等信息的格式,可视为日志记录的格式控制。
2.4.1 两种构造方法
方法一:直接通过模块级方法进行构造
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
"""
参数说明:
fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
datefmt: 指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
styple: 可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
"""
方法二:定义Formatter类,并绑定于Handler
formatter = logging.Formatter(fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
handler = logging.Handler()
handler.setFormatter(formatter)
2.5 各组件间的逻辑关系
上述四大组件间的相互逻辑关系见下图:
2.6 一个简单的示例
def my_logger():
"""
共配置了两个Logger对象,父Logger A和子Logger A.B
父Logger A配置了一个StreamHandler,用于将日志输出文件
子Logger A.B配置了两个FileHandler,用于分别对日志的level进行控制
:return:
"""
logger_parent = logging.getLogger(name='A') # 父logger
logger_child = logging.getLogger(name='A.B') # 子logger
# logger_child.propagate = 0 # 防止反传消息
logger_parent.setLevel('DEBUG') # 必须先设置logger级的level,再设置handler级的,否则会出现错误
stream_handler = logging.StreamHandler() # 流文件处理器,此处用于控制写入A.B logger
stream_handler.setLevel(logging.WARNING) # handler的level设置
stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) # handler的format设置
file_handler1 = logging.FileHandler('logging_Logger_sample_logger1.log', encoding='utf-8', mode='a+')
file_handler1.setLevel(logging.INFO)
file_handler1.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
file_handler2 = logging.FileHandler('logging_Logger_sample_logger2.log', encoding='utf-8', mode='a+')
file_handler2.setLevel(logging.WARNING)
file_handler2.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger_parent.addHandler(stream_handler)
logger_child.addHandler(file_handler1)
logger_child.addHandler(file_handler2)
logger_parent.warning("parent_logger警告!")
logger_parent.info('parent_logger消息!')
logger_parent.debug('parent_logger调试!')
logger_child.warning("child_logger警告!")
logger_child.info('child_logger消息!')
logger_child.debug('child_logger调试!')
logger_child.error('child_logger错误!')
2.7 另一个封装的示例(按UUID进行日志记录归档)
import os
import logging
import uuid
from logging import Handler, FileHandler, StreamHandler
class PathFileHandler(FileHandler):
def __init__(self, path, filename, mode='a', encoding='utf-8', delay=False):
filename = os.fspath(filename)
if not os.path.exists(path):
os.mkdir(path)
self.baseFilename = os.path.join(path, filename)
self.mode = mode
self.encoding = encoding
self.delay = delay
if delay:
Handler.__init__(self)
self.stream = None
else:
StreamHandler.__init__(self, self._open())
class Loggers(object):
# 日志级别关系映射
level_relations = {
'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING,
'error': logging.ERROR, 'critical': logging.CRITICAL
}
def __init__(self, filename='{uid}.log'.format(uid=uuid.uuid4()), level='info', log_dir='log',
fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
self.logger = logging.getLogger(filename)
abspath = os.path.dirname(os.path.abspath(__file__))
self.directory = os.path.join(abspath, log_dir)
format_str = logging.Formatter(fmt) # 设置日志格式
self.logger.setLevel(self.level_relations.get(level)) # 设置日志级别
stream_handler = logging.StreamHandler() # 往屏幕上输出
stream_handler.setFormatter(format_str)
file_handler = PathFileHandler(path=self.directory, filename=filename, mode='a')
file_handler.setFormatter(format_str)
self.logger.addHandler(stream_handler)
self.logger.addHandler(file_handler)
if __name__ == "__main__":
txt = "用于测试日志的文本数据"
log = Loggers(level='debug')
log.logger.info(1)
log.logger.error("出现error")
log.logger.info(txt)
三、通过fileConfig读入config文件
3.1 相关文件的配置
下面检出一份简单的logging.conf
配置文件和调用的py
文件案例。
logging.conf
配置文件
[loggers]
keys=root,simpleExample
[handlers]
keys=fileHandler,consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=fileHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter
[handler_fileHandler]
class=FileHandler
args=('logging_fileconfig_sample_logging.log', 'a')
level=ERROR
formatter=simpleFormatter
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
- 调用的
py
文件
import logging.config
# 读取日志配置文件内容
logging.config.fileConfig('logging.conf')
# 创建一个日志器logger
logger = logging.getLogger('simpleExample')
# 日志输出
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
3.2 logging.conf
配置文件的说明
通过logging.conf
进行日志配置,其实就是对python中自带的configparser库的封装,所以logging.conf
对于section
和option
的书写格式及其他要求均可参考该库。
读取配置文件的API为:
logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)
"""
fname: 配置文件路径或名称
defaults:指定传给ConfigParser的默认值
disable_existing_loggers:布尔型值,默认值为True(为了向后兼容)表示禁用已经存在的logger;如果值为False则对已存在的loggers保持启动状态。
"""
在编写配置文件fname
时候,需要注意如下事项:
(1)配置文件中一定要包含loggers、handlers、formatters这些section,它们通过keys这个option来指定该配置文件中已经定义好的loggers、handlers和formatters,多个值之间用逗号分隔;
(2)loggers这个section中的keys一定要包含root这个值;
(3)loggers、handlers、formatters中所指定的日志器、处理器和格式器都需要在下面以单独的section进行定义。seciton的命名规则为[logger_loggerName]、[formatter_formatterName]、[handler_handlerName]
(4)定义logger的section必须指定level和handlers这两个option。其中handlers的值是以逗号分隔的handler名字列表(若只有1个,也可单独给出),这里出现的handler必须出现在[handlers]这个section中,并且相应的handler必须在配置文件中有对应的section定义;
(5)对于非root logger来说,除了level和handlers这两个option之外,还需要一些额外的option,其中qualname是必须提供的option,它表示在logger层级中的名字,在应用代码中通过这个名字得到logger;控制反馈的参数propagate是可选项,其默认是为1;
(6)定义handler的section中必须指定class和args这两个option,level和formatter为可选option;class表示用于创建handler的类名,args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;level与logger中的level一样,而formatter指定的是该处理器所使用的格式器,这里指定的格式器名称必须出现在formatters这个section中,且在配置文件中必须要有这个formatter的section定义;如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间、日志器名称等信息;
(7)定义formatter的sectioin中的option都是可选的,其中包括format用于指定格式字符串,默认为消息字符串本身;datefmt用于指定asctime的时间格式,默认为’%Y-%m-%d %H:%M:%S’;class用于指定格式器类名,默认为logging.Formatter;
(8)配置文件中的class
指定类名时,该类名可以是相对于logging模块的相对值,如:FileHandler、handlers.TimeRotatingFileHandler;也可以是一个绝对路径值,通过普通的import机制来解析(注意路径一定要在sys.path
包含的路径中)。
四、通过dictConfig加载json、yaml等配置文件
4.1 加载json配置文件
logging.json
配置文件
{
"version":1,
"disable_existing_loggers":false,
"formatters":{
"simple":{
"format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers":{
"console":{
"class":"logging.StreamHandler",
"level":"DEBUG",
"formatter":"simple",
"stream":"ext://sys.stdout"
},
"info_file_handler":{
"class":"logging.handlers.RotatingFileHandler",
"level":"INFO",
"formatter":"simple",
"filename":"info.log",
"maxBytes":"10485760",
"backupCount":20,
"encoding":"utf8"
},
"error_file_handler":{
"class":"logging.handlers.RotatingFileHandler",
"level":"ERROR",
"formatter":"simple",
"filename":"errors.log",
"maxBytes":10485760,
"backupCount":20,
"encoding":"utf8"
}
},
"loggers":{
"my_module":{
"level":"ERROR",
"handlers":["info_file_handler"],
"propagate":"no"
}
},
"root":{
"level":"INFO",
"handlers":["console","info_file_handler","error_file_handler"]
}
}
- 加载的
python
文件
import json
import logging.config
import os
def setup_logging(default_path = "logging.json",default_level = logging.INFO,env_key = "LOG_CFG"):
path = default_path
value = os.getenv(env_key,None)
if value:
path = value
if os.path.exists(path):
with open(path,"r") as f:
config = json.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level = default_level)
def func():
logging.info("start func")
logging.info("exec func")
logging.info("end func")
if __name__ == "__main__":
setup_logging(default_path = "logging.json")
func()
4.2 加载yaml配置文件
logging.yaml
配置文件
version: 1
disable_existing_loggers: False
formatters:
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
info_file_handler:
class: logging.handlers.RotatingFileHandler
level: INFO
formatter: simple
filename: info.log
maxBytes: 10485760
backupCount: 20
encoding: utf8
error_file_handler:
class: logging.handlers.RotatingFileHandler
level: ERROR
formatter: simple
filename: errors.log
maxBytes: 10485760
backupCount: 20
encoding: utf8
loggers:
my_module:
level: ERROR
handlers: [info_file_handler]
propagate: no
root:
level: INFO
handlers: [console,info_file_handler,error_file_handler]
- 加载的
python
文件
import yaml
import logging.config
import os
def setup_logging(default_path = "logging.yaml",default_level = logging.INFO,env_key = "LOG_CFG"):
path = default_path
value = os.getenv(env_key,None)
if value:
path = value
if os.path.exists(path):
with open(path,"r") as f:
config = yaml.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level = default_level)
def func():
logging.info("start func")
logging.info("exec func")
logging.info("end func")
if __name__ == "__main__":
setup_logging(default_path = "logging.yaml")
func()
五、利用logging模块记录程序异常
利用logging模块可以记录异常回溯信息 traceback.format_exc()
,其可行的API接口为loging.exception(msg, args)
或者logging.error(msg,exec_info=true,args)
(两者是等价的)。
- 举个例子
import logging
def logger_traceback():
logger = logging.getLogger('traceback')
logger.setLevel(level=logging.INFO)
handler = logging.FileHandler("trace_log.txt", encoding='utf-8')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(handler)
logger.addHandler(console)
logger.info("开始记录日志信息!")
logger.debug("调试程序!")
logger.warning("出现某些异常!")
try:
open("destination.txt", "rb")
except (SystemExit, KeyboardInterrupt):
raise
except Exception:
logger.error("无法打开destination.txt", exc_info=True)
logger.info("日记记录结束!")
if __name__ == '__main__':
logger_traceback()
本文中的相关案例下载请戳这里
Reference: