文章目录
开发部署项目时,不可能将所有的信息都输出到控制台中,因此我们将这些信息记录到日志文件中,不仅方便查看程序运行的情况,也可以在项目出现故障时根据该运行时产生的日志快速定位问题。
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
: 必要参数,必须为1
incremental
:是否将此配置文件解释为现有配置的增量, 默认为False
disable_existing_loggers
:是否要禁用现有的非root logger
,默认为True
handlers
:字典形式class
:必须有,所使用的handler
类,如logging.handlers.RotatingFileHandler
level
:(可选),handler
的等级formatter
:(可选),当前handler
的formatter
的id
filters
:(可选),当前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- - 博客园