文章目录
开发部署项目时,不可能将所有的信息都输出到控制台中,因此我们将这些信息记录到日志文件中,不仅方便查看程序运行的情况,也可以在项目出现故障时根据该运行时产生的日志快速定位问题。
Python中提供了日志模块logging,可以方便的记录日志信息。
本文主要分析了
logging模块的相关知识及具体使用。
当前介绍该日志模块的文章有很多,之所以还要进行记录,一方面是因为没有看到系统的介绍,二是通过梳理此部分内容,做到不止会用,还明白内部函数调用。
1、日志级别
Python标准库logging用做记录日志,默认分为五种日志级别:
DEBUG(10):详细信息,调试问题时所需INFO(20):证明事情可按预期工作WARNING(30):有意外,将来可能发生问题,但依然可用ERROR(40):严重问题,程序不能执行一些功能CRITICAL(50):严重错误
我们自定义日志级别时注意不要和默认的日志级别数值型相同(这是什么意思呢),logging执行时输出大于等于设置的值日级别的日志信息,如设置级别为INFO,则INFO, WARNING, ERROR, CRITICAL级别的日志都会输出。
5种等级分别对应5种日志打印方法:debug, info, warning, error, critical。
默认的是WARNING,即当等级大于等于WARNING才被跟踪。
2、logging流程
官方的logging模块工作流程图如下:

从上图中可以看到这几种python类型,Logger, LogRecord, Filter, Handler, Formatter。
类型说明
Logger:日志,暴露函数给应用程序,基于日志记录器和过滤器级别决定哪些日志有效。LogRecord:日志记录器,将日志传到相应的处理器处理。Handler:处理器,将(日志记录器产生的)日志记录发送至合适的目的地。Filter:过滤器,提供了更好的粒度控制,它可以决定输出哪些日志记录。Formatter:格式化器,指明了最终输出中日志记录的布局。
基本流程:
- 判断
Logger对象对于设置的级别是否可用,如果可用,则往下执行,否则,流程结束。 - 创建
LogRecord对象,如果注册到Logger对象中的Filter对象过滤后返回False, 则不记录日志,流程结束,否则,向下执行。 LogRecord对象将Handler对象传入当前的Logger对象,(图中子流程)如果Handler对象的日志级别大于设置的日记级别,再判断注册到Handler对象中的Filter对象过滤后是否返回True而放行输出日志信息,否则不放行,流程结束。- 如果传入的
Handler大于Logger中设置的级别,也即Handler有效,则往下执行,否则,流程结束。 - 判断这个
Logger对象是否还有父Logger对象,如果没有(代表当前Logger对象是最顶层的Logger对象root Logger),流程结束。否则将Logger对象设置为它的父Logger对象,重复上面3,4两步,输出父类Logger对象中的日志输出,直到是root Logger对象。
3、几个重要的类
前面讲logging流程的时候,提到几个概念:Logger,Handler,Filter,Formatter。
3.1 Logger类
Logger对象有3个任务要做:
- 向应用程序代码暴露
info, debug等方法,使应用程序可以在运行时记录日志消息 - 基于日志级别(默认的过滤设施)或
Filter对象来决定要对哪些日志进行后续处理 - 将日志消息传送给所有感兴趣的日志
handlers
一个系统只有一个根Logger对象,并且该对象不能被直接实例化。
通过调用
logging.getLogger(name)函数创建Logger类对象。
Logger对象最常用的方法有两类:(1)配置方法,(2)消息发送方法
Logger对象可以设置多个Handler对象和Filter对象,Handler对象可以设置Formatter对象。
Formatter对象用来设置消息的具体输出格式。
1) 最常用的配置方法
| 方法 | 描述 |
|---|---|
Logger.setLevel | 设置日志器将会处理的日志消息的最低严重级别 |
Logger.addHandler() | 为该Logger对象添加一个handler对象 |
Logger.removeHandler | 为该Logger对象移除一个handler对象 |
Logger.addFilter()和Logger.removeFilter() | 为该Logger对象添加 和 移除一个filter对象 |
logger对象配置完成后,可使用下面的方法来创建日志记录:
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical():创建一个与他们的方法名对应等级的日志记录Logger.exception():创建一个类似于Logger.error()的日志消息Logger.log():需要获取一个明确的日志level参数来创建一个日志记录
1. 获取Logger对象
通常是通过--logging.getLogger()的方法。该方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为root。
若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个Logger对象的引用。
2. logger的层级结构与有效等级
-
logger的名称是一个以'.'分割的层级结构,每个'.'后面的logger都是’.'前面的logger的children,例如,有一个名称为foo的logger,其它名称分别为foo.bar, foo.bar.baz和foo.bam都是foo的后代。 -
logger有一个"有效等级(effective level)"的概念。如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parent的parent的有效level,依次类推,直到找到个一个明确设置了level的祖先为止。需要说明的是,root logger总是会有一个明确的level设置(默认为WARNING)。当决定是否去处理一个已发生的事件时,logger的有效等级将会被用来决定是否将该事件传递给该logger的handlers进行处理。 -
child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
2) 创建Logger对象logging.getLogger([name])
日志记录的工作主要由Logger对象来完成。前面讲到一个系统只有一个根Logger对象,并且该对象不能被直接实例化。
需要通过logging.getLogger([name])来获取Logger对象。
在调用getLogger时要提供Logger的名称(多次使用相同名称来调用getLogger,返回的是同一个对象的引用。),Logger实例之间有层次关系,这些关系通过Logger名称来体现。
p = logging.getLogger(“root”)
c1 = logging.getLogger(“root.c1”)
c2 = logging.getLogger(“root.c2”)
例子中,p是父logger, c1,c2分别是p的子logger。c1, c2将继承p的设置。如果省略了name参数, getLogger将返回日志对象层次关系中的根Logger。
每个Logger对象都可以设置一个名字,如果设置logger = logging.getLogger(__name__),__name__是Python中的一个特殊内置变量,他代表当前模块的名称(默认为__main__)。
该函数内部根据是否指定名称返回对应的Logger对象。
root = RootLogger(WARNING)
Logger.root = root
Logger.manager = Manager(Logger.root)
def getLogger(name=None):
"""
Return a logger with the specified name, creating it if necessary.
If no name is specified, return the root logger.
"""
if name:
return Logger.manager.getLogger(name)
else:
return root
示例:
import logging
logger = logging.getLogger('test')
logger.warning('this is a warning info')
# 输出
WARNING:test:this is a warning info
3.2 Handler类
Handler对象的作用是(基于日志消息的等级)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加handler对象。
应用程序代码不应该直接实例化和使用
Handler实例。因为Handler是一个基类,只定义了所有handlers都应该有的接口,同时提供了一些子类可以直接使用或覆盖的默认行为。常用接口有:
setLevel()setFormatter()set_name()
常用的Handler:
| 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'信息的出现。 |
类之间的继承关系如下:

示例:
import logging
logger = logging.getLogging() # 获取Logger对象
handler = logging.FileHandler('output.log', mode='a', encoding=None, delay=False)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
style='%')
handler.setFormatter(formatter) # handler对象设置格式
handler.setLevel(logging.DEBUG)# handler设置日志级别
logger.addHandler(handler) # 添加handler对象
3.3 Filter类
Filter实例用于执行日志记录的任意筛选。
Loggers和Handlers可以根据需要使用筛选器实例筛选记录.
logging标准库只提供了一个base filter,其构造函数__init__接收name,默认的行为是名为name的logger及其子logger的消息能通过过滤器,其余的被过滤掉。
class Filter(object):
"""
Filter instances are used to perform arbitrary filtering of LogRecords.
"""
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)
3.4 Formatter类
Formatter对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。
Formatter类的构造方法定义如下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
该方法可接收3个可选参数:
fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值datefmt:指定日期格式字符串,如果不指定,则默认使用"%Y-%m-%d %H:%M:%S"style:可取值为'%', '{'和'$',如果不指定,则默认使用'%'
| 格式 | 含义 |
|---|---|
%(levelno)s | 打印日志级别的数值 |
%(levelname)s | 打印日志级别名称 |
%(pathname)s | 打印当前执行程序的路径 |
%(filename)s | 打印当前执行程序名 |
%(funcName)s | 打印日志的当前函数 |
%(lineno)d | 打印日志的当前行号 |
%(asctime)s | 打印日志的时间 |
%(thread)d | 打印线程ID |
%(message)s | 打印日志信息 |
%(process)s | 当前进程,进程ID |
%(module)s | 调用日志输出函数的模块名,filename的名称部分,不包含后缀 |
%(msecs)d | 日志事件发生事件的毫秒部分。logging.basicConfig()中用参数datefmt将会去掉asctime中产生的毫秒部分,可以用这个加上。 |
一般直接使用logging.Formatter(fmt, datefmt)
import logging
fmt = "%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s"
datefmt="%Y-%m-%d %H:%M:%S"
logFormatter = logging.Formatter(fmt, datefmt)
4. 使用示例
该部分主要展示如何使用
logging模块记录日志信息的使用方法。
默认的输出格式如下:

示例:

logging模块如何使用呢?下面介绍几种常用的使用方式:
一是使用logging的对外函数接口;二是使用basicConfig()方法,该方法能够满足基本的使用需求;三是通过创建Logger对象,可以进行更加复杂的处理。
4.1 直接调用logging接口
最简单的使用方法,就是直接调用logging标准库的接口,如logging.debug(),logging.info()等函数。
默认的日志级别是WARNING,当等级大于等于WARNING才被跟踪。
内部默认调用的是
root.log(level, mas)函数
接口函数如下:
debug()info()warning()error()critical()
也可以直接使用同一接口logging.log(),输入级别及具体信息。
log(level, msg):level表示日志级别,输入日志级别对应的整数或代码,如下:CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0
这些接口函数内部实际调用的都是Logger类的对应函数。如果logger没有handler时,可默认调用basicConfig()添加控制台handler。
如,logging.info()函数内部调用root.info()函数,如下
def info(msg, *args, **kwargs):
"""
Log a message with severity 'INFO' on the root logger. If the logger has
no handlers, call basicConfig() to add a console handler with a pre-defined
format.
"""
if len(root.handlers) == 0:
basicConfig()
root.info(msg, *args, **kwargs)
示例如下:
import logging
logging.warning('this is a warning info.')
logging.error('this is a error info.')
logging.debug('this is a debuginfo.')
# 输出
WARNING:root:this is a warning info.
ERROR:root:this is a error info.
logging.log(20, 'this is a info msg.')
logging.log(30, 'this is a warn msg.')
# 输出:
WARNING:root:this is a warn msg.
记录具体异常信息
当发生异常时,直接使用无参数的debug(), info(), warning(), error(), critical()方法并不能记录异常信息。
我们看一下各个接口的定义及参数:
# 普通接口
def warning(msg, *args, **kwargs)
# 内部调用如下函数:
self._log(WARNING, msg, args, **kwargs)
# 统一接口
def log(level, msg, *args, **kwargs)
# 内部调用如下函数:
self._log(level, msg, args, **kwargs)
# exception函数,参数中默认exc_info为True
def exception(msg, *args, exc_info=True, **kwargs):
error(msg, *args, exc_info=exc_info, **kwargs)
# 内部调用如下函数:
self._log(ERROR, msg, args, **kwargs)
由上面代码可见,其内部都是调用函数Logger._log(level, msg, args, **kwargs)。不同的是参数,如level日志级别。
函数Logger._log()的定义如下:
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False)
可见,其除了level, msg以外,还有参数exc_info。该参数用来控制是否打印具体信息。
函数exception()就等于error(msg, exc_info=True)
因此:
记录异常信息的方法:
- 设置
exc_info参数为True,指示是否打印执行信息 - 或者使用
exception()方法,同时记录当前的堆栈追踪(仅从异常处理程度调用此方法) - 还可以使用
log()方法,但是要设置日志级别和exce_info参数。
logger.log(level, msg, exc_info=True)是各个接口函数的统一调用方式。
示例代码:
import logging
logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)
a = 5
b = 0
try:
c = a / b
except Exception as e:
# 下面三种方式三选一,推荐使用第一种
logging.exception("Exception occurred")
logging.error("Exception occurred", exc_info=True)
logging.log(level=logging.DEBUG, msg="Exception occurred", exc_info=True)
输出文件test.log的内容:

4.2 使用basicConfig()函数
使用basicConfig()方法就能够满足基本的使用需要,如果方法没有传入参数,会根据默认的配置创建Logger对象,默认的日志级别被设置为 WARNING 。
默认的日志输出格式:
| WARNING | root | message |
|---|---|---|
| 日志级别 | Logger实例 | 日志信息 |
函数定义:
def basicConfig(**kwargs)
该函数可选的参数如下表所示:
| 参数名称 | 参数描述 |
|---|---|
filename | 日志输出到文件的文件名 |
filemode | 文件模式,r[+]、w[+]、a[+] |
format | 日志输出的格式 |
datefmt | 日志附带日期时间的格式 |
style | 格式占位符,默认为“%”和“{}” |
level | 设置日志输出级别 |
stream | 定义输出流,用来初始化 StreamHandler对象,不能和 filename参数一起使用,否则会ValueError异常 |
handles | 定义处理器,用来创建Handler对象,不能和filename,stream参数一起使用,否则也会抛出ValueError异常 |
stream输出流不与filename输出文件一起使用handler处理器不与stream输出流或filename输出文件一起用- 即
stream、filename和handler互相排斥- 若想将日志信息同时输出到文件和控制台,则需要使用
Logger处理器增加对应的处理方法。
basicConfig()方法可以实现将日志信息输出到文件中或者输出到定义的输出流中。
- 输出到文件中,可以通过
filename参数,或者通过handler处理器创建相应文件对象实现- 内部创建
FileHandle()对象
- 内部创建
- 打印到输出流中,使用默认参数即可
- 内部创建
StreamHandler()对象
- 内部创建
其中,format指定输出的格式和内容,format可以输出很多有用信息,具体格式见Formatter类。
对 basicConfig()的调用应该在 debug(), info() 等的前面。因为它被设计为一次性的配置,只有第一次调用会进行操作,随后的调用不会产生有效操作。
该方法相当于显示调用basicConfig()进行参数的配置,而直接调用logging.info()接口函数,若没有handler时,是隐式调用basicConfig()的默认参数形式。
1)日志信息打印到输出流
import logging
# 直接调用basicConfig()函数,使用默认参数
logging.basicConfig()
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
# 输出:(默认级别是WARNING)
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message
2)格式化输出
格式化输出时间:
通过datefmt参数可以格式化输出log的时间。
常用的输出格式如下:
format = '%(asctime)s - %(filename)s[line:%(lineno)d]- %(levelname)s: %(message)s'
该格式可以输出日志的打印时间,输出模块及行号,日志级别和输出的日志内容。
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt = '%Y-%m-%d %I:%M:%S %p')
logging.warning('is when this event was occurred.')
# 输出结果
2019-10-15 04:03:17 PM is when this event was occurred.
3) 将日志输出到指定文件
指定filename, filemode参数,可将日志内容输出到指定文件,示例代码如下:
import logging
logging.basicConfig(filename='test.log', filemode='w', format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
生成的日志文件test.log,内容如下:

4.3 一步步配置Logger
basicConfig函数提供了一种较为简便的使用日志的方式,如果想要获取更加复杂的使用,则需要自己一步步配置来实现。
配置逻辑:
- 一个
logger对象可以添加多个handler对象,通过addHandler()函数 - 每个
handler对象可以有一个Formatter对象来指定格式,通过setFormatter()函数 handler和logger对象都需要设置一个日志等级,通过setLevel()函数- 根据需要
logger和handler对象可以添加多个Filter对象,通过addFilter()函数
示例:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
handler = logging.FileHandler('output.log', mode='a', encoding=None, delay=False)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
style='%')
handler.setFormatter(formatter)
handler.setLevel(logging.DEBUG)
handler.set_name('output-log')
logger.addHandler(handler)
a=0
try:
res = 100 / a
except:
logger.error('Divisor is equal to 0.', exc_info=True)
将日志同时输出到屏幕和文件
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.DEBUG)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(handler)
logger.addHandler(console)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
在log.txt文件和控制台均可看到输出,控制台的输出,其中文件中内容(有格式)如下:

通过这段代码可以看出,logging有一个日志处理的主对象(logger=logging.getLogger()),其他的处理方法均是通过addHandler添加进去。
4.4 通过dictConfig配置
通过dictConfig配置方法是通过python代码构建一个dict对象,也可以通过JSON或yaml文件来进行配置。
这种方式使用起来更加灵活,强大。我们可以把很多的数据转换成自字典形式,然后将他们填充到一个配置字典中。
具体如何转换可以参考logging.config.py文件。
4.4.1 dictConfig()
该函数可以从一个字典对象中获取日志配置信息,config参数就是这个字典对象。
定义如下:
def dictConfig(config):
"""Configure logging using a dictionary."""
dictConfigClass(config).configure()
如使用YAML格式的文件类配置(配置内容见下方示例),则使用示例:
import logging
import logging.config
import yaml
with open('logging.yml', 'r') as f_config:
dict_cfg = yaml.load(f_config)
logging.config.dictConfig(dict_cfg)
logger = logging.getLogger('console')
logger.debug('debug msg')
4.4.2 配置规则
| key名称 | 描述 |
|---|---|
version | 必选项,整数值,表示配置格式版本,当前唯一可用值是1 |
formatters | 可选项,其值是字典对象,该字典对象每个元素的key为要定义的格式器名称,value为格式器的配置信息组成的dict。一般会配置format,用于指定输出字符串的格式,datefmt用于指定输出的时间字符串格式,默认为%Y-%m-%d %H:%M:%S。 |
filters | 可选项,其值是字典对象,该字典对象每个元素的key为要定义的过滤器名称,value为过滤器的配置信息组成的dict。 |
handlers | 可选项,其值是字典对象,该字典对象每个元素的key为要定义的处理器名称,value为处理器的配置信息组成的dict。如class(必选项), formatter, filters。其他配置信息将会传递给class所指定的处理器类的构造函数。如使用logging.handlers.RotatingFileHandler,使用maxBytes, backupCount等参数。 |
loggers | 可选项,其值是字典对象,该字典对象每个元素的key为要定义的日志器名称,value为日志器的配置信息组成的dict。如level, handler, filter等 |
root | 可选项,这是root logger的配置项,其值是字典对象。除非在定义其它logger时明确指定propagate值为no,否则root logger定义的handlers都会被作用到其它logger上。 |
incremental | 可选项,默认值为False。该选项的意义在于,如果这里定义的对象已经存在,那么这里对这些对象的定义是否应用到已存在的对象上。值为False表示,已存在的对象将会被重新定义。 |
具体配置结构如下所示:
version: 必要参数,必须为1incremental:是否将此配置文件解释为现有配置的增量, 默认为Falsedisable_existing_loggers:是否要禁用现有的非root logger,默认为Truehandlers:字典形式class:必须有,所使用的handler类,如logging.handlers.RotatingFileHandlerlevel:(可选),handler的等级formatter:(可选),当前handler的formatter的idfilters:(可选),当前handler的filters的id列表
loggers:字典形式level:(可选),logger的等级propagate:(可选):默认值为1,表示将消息传递给父logger的handler进行处理。filters:(可选)handlers:(可选) ,当前logger的handlers的id列表qualname:代码中使用logging.getLogger()时,读取的就是qualname这个选项。
root:这是root logger的配置项level:日志等级handlers:当前root logger的handlers的id列表
4.4.3 配置示例
version:1
root:
level:DEBUG
handlers:[filehandler, ]
loggers:
console:
level:WARNING
handlers:[consolehandler,]
propagate:1
handlers:
filehandler:
class:logging.FileHandler
filename:logs/sys.log
level:WARNING
formatter:fileformatter
consolehandler:
class:logging.StreamHandler
stream:ext://sys.stdout
level:DEBUG
formatter:consoleformatter
formatters:
fileformatter:
format:'%(asctime)s [%(name)s][%(levelname)s]:%(message)s'
consoleformatter:
format:'%(asctime)s[%(levelname)s]:%(message)s'
外部对象访问:外部对象是指不能直接进行访问,需要
import导入的对象,这时候需要加一个ext://前缀以便识别,然后系统就会import这个前缀后面的内容。
参考资料:
Python日志库logging总结-可能是目前为止将logging库总结的最好的一篇文章
python基础学习十 logging模块详细使用【转载】 - louis_w - 博客园
Python之路(第十七篇)logging模块 - Nicholas- - 博客园
本文深入解析Python的logging模块,涵盖日志级别、流程、关键类(Logger、Handler、Filter、Formatter)的使用与配置,包括示例代码和最佳实践。
17万+

被折叠的 条评论
为什么被折叠?



