Python 日志模块logging分析及使用-1

3,400 阅读23分钟

内容目录

一、日志级别二、logging流程三、几个重要的类3.1 Logger类3.1.1 最常用的配置方法3.1.2 创建Logger对象logging.getLogger([name])3.2 Handler类3.3 Filter类3.4 Formatter类四、使用示例4.1 直接调用logging接口4.2 使用basicConfig()函数4.3 一步步配置Logger4.4 通过dictConfig配置4.4.1 dictConfig()4.4.2 配置规则4.4.3 配置示例

开发部署项目时,不可能将所有的信息都输出到控制台中,因此我们将这些信息记录到日志文件中,不仅方便查看程序运行的情况,也可以在项目出现故障时根据该运行时产生的日志快速定位问题。

Python中提供了日志模块logging,可以方便的记录日志信息。

本文主要分析了logging模块的相关知识及具体使用。
当前介绍该日志模块的文章有很多,之所以还要进行记录,一方面是因为没有看到系统的介绍,二是通过梳理此部分内容,做到不止会用,还明白内部函数调用。

一、日志级别

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才被跟踪。

二、logging流程

官方的logging模块工作流程图如下:

logging流程图
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对象。

三、几个重要的类

前面讲logging流程的时候,提到几个概念:LoggerHandlerFilterFormatter

3.1 Logger类

Logger对象有3个任务要做:

  • 向应用程序代码暴露info, debug等方法,使应用程序可以在运行时记录日志消息
  • 基于日志级别(默认的过滤设施)或Filter对象来决定要对哪些日志进行后续处理
  • 将日志消息传送给所有感兴趣的日志handlers

一个系统只有一个根Logger对象,并且该对象不能被直接实例化

通过调用logging.getLogger(name)函数创建Logger类对象。

Logger对象最常用的方法有两类:(1)配置方法,(2)消息发送方法

Logger对象可以设置多个Handler对象和Filter对象,Handler对象可以设置Formatter对象。
Formatter对象用来设置消息的具体输出格式。

3.1.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都是'.'前面的loggerchildren,例如,有一个名称为 foologger,其它名称分别为foo.bar, foo.bar.bazfoo.bam都是 foo 的后代。

  • logger有一个"有效等级(effective level"的概念。如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parentparent的有效level,依次类推,直到找到个一个明确设置了level的祖先为止。需要说明的是,root logger总是会有一个明确的level设置(默认为 WARNING。当决定是否去处理一个已发生的事件时,logger的有效等级将会被用来决定是否将该事件传递给该loggerhandlers进行处理。

  • child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,只需要为一个顶层的`logger`配置`handlers`,然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。

3.1.2 创建Logger对象logging.getLogger([name])

日志记录的工作主要由Logger对象来完成。前面讲到一个系统只有一个根Logger对象,并且该对象不能被直接实例化

需要通过logging.getLogger([name])来获取Logger对象。
在调用getLogger时要提供Logger的名称(多次使用相同名称来调用getLogger,返回的是同一个对象的引用。),Logger实例之间有层次关系,这些关系通过Logger名称来体现。

1p = logging.getLogger(“root”)
2c1 = logging.getLogger(“root.c1”)
3c2 = logging.getLogger(“root.c2”)

例子中,p父logger, c1,c2分别是p的子loggerc1, c2将继承p的设置。如果省略了name参数, getLogger将返回日志对象层次关系中的根Logger

每个Logger对象都可以设置一个名字,如果设置logger = logging.getLogger(__name__)__name__Python中的一个特殊内置变量,他代表当前模块的名称(默认为__main__)。

该函数内部根据是否指定名称返回对应的Logger对象

 1root = RootLogger(WARNING)
2Logger.root = root
3Logger.manager = Manager(Logger.root)
4
5def getLogger(name=None):
6    """
7    Return a logger with the specified name, creating it if necessary.
8    If no name is specified, return the root logger.
9    """

10    if name:
11        return Logger.manager.getLogger(name)
12    else:
13        return root

示例:

1import logging
2logger = logging.getLogger('test')
3logger.warning('this is a warning info')
4# 输出
5WARNING: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 将日志消息以GETPOST的方式发送给一个HTTP服务器
logging.handlers.SMTPHandler 将日志消息发送给一个指定的email地址
logging.NullHandler Handler实例会忽略error messages,通常被想使用logginglibrary开发者使用来避免'No handlers could be found for logger XXX'信息的出现。

类之间的继承关系如下:

在这里插入图片描述
在这里插入图片描述

示例:

 1import logging
2
3logger = logging.getLogging() # 获取Logger对象
4handler = logging.FileHandler('output.log', mode='a', encoding=None, delay=False)
5formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',
6                              datefmt='%Y-%m-%d %H:%M:%S',
7                              style='%')
8handler.setFormatter(formatter) # handler对象设置格式
9handler.setLevel(logging.DEBUG)# handler设置日志级别
10
11logger.addHandler(handler) # 添加handler对象

3.3 Filter类

Filter实例用于执行日志记录的任意筛选。
LoggersHandlers可以根据需要使用筛选器实例筛选记录.
logging标准库只提供了一个base filter,其构造函数__init__接收name,默认的行为是名为namelogger及其子logger的消息能通过过滤器,其余的被过滤掉。

 1class Filter(object):
2    """
3    Filter instances are used to perform arbitrary filtering of LogRecords.
4    """

5    def __init__(self, name=''):
6        """
7        Initialize a filter.
8
9        Initialize with the name of the logger which, together with its
10        children, will have its events allowed through the filter. If no
11        name is specified, allow every event.
12        """

13        self.name = name
14        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)

1import logging
2fmt = "%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s"
3datefmt="%Y-%m-%d %H:%M:%S"
4logFormatter = logging.Formatter(fmt, datefmt)

四、使用示例

该部分主要展示如何使用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表示日志级别,输入日志级别对应的整数或代码,如下:
1  CRITICAL = 50
2  FATAL = CRITICAL
3  ERROR = 40
4  WARNING = 30
5  WARN = WARNING
6  INFO = 20
7  DEBUG = 10
8  NOTSET = 0

这些接口函数内部实际调用的都是Logger类的对应函数。如果logger没有handler时,可默认调用basicConfig()添加控制台handler
如,logging.info()函数内部调用root.info()函数,如下

1def info(msg, *args, **kwargs):
2    """
3    Log a message with severity 'INFO' on the root logger. If the logger has
4    no handlers, call basicConfig() to add a console handler with a pre-defined
5    format.
6    """

7    if len(root.handlers) == 0:
8        basicConfig()
9    root.info(msg, *args, **kwargs)

示例如下:

 1import logging
2logging.warning('this is a warning info.')
3logging.error('this is a error info.')
4logging.debug('this is a debuginfo.')
5# 输出
6WARNING:root:this is a warning info.
7ERROR:root:this is a error info.
8
9logging.log(20'this is a info msg.')
10logging.log(30'this is a warn msg.')
11# 输出:
12WARNING:root:this is a warn msg.

记录具体异常信息

当发生异常时,直接使用无参数的debug(), info(), warning(), error(), critical()方法并不能记录异常信息。

我们看一下各个接口的定义及参数:

 1# 普通接口
2def warning(msg, *args, **kwargs)
3# 内部调用如下函数:
4self._log(WARNING, msg, args, **kwargs)
5
6# 统一接口
7def log(level, msg, *args, **kwargs)
8# 内部调用如下函数:
9self._log(level, msg, args, **kwargs)
10
11exception函数,参数中默认exc_infoTrue
12def exception(msg, *args, exc_info=True, **kwargs):

13   error(msg, *args, exc_info=exc_info, **kwargs)
14# 内部调用如下函数:
15self._log(ERROR, msg, args, **kwargs)

由上面代码可见,其内部都是调用函数Logger._log(level, msg, args, **kwargs)。不同的是参数,如level日志级别。
函数Logger._log()的定义如下:

1def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False)
2

可见,其除了levelmsg以外,还有参数exc_info。该参数用来控制是否打印具体信息。

函数exception()就等于error(msg, exc_info=True)

因此:
记录异常信息的方法

  • 设置exc_info参数为True,指示是否打印执行信息
  • 或者使用exception()方法,同时记录当前的堆栈追踪(仅从异常处理程度调用此方法)
  • 还可以使用log()方法,但是要设置日志级别和exce_info参数。

logger.log(level, msg, exc_info=True)是各个接口函数的统一调用方式。

示例代码:

 1import logging
2
3logging.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)
4a = 5
5b = 0
6try:
7    c = a / b
8except Exception as e:
9    # 下面三种方式三选一,推荐使用第一种
10    logging.exception("Exception occurred")
11    logging.error("Exception occurred", exc_info=True)
12    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输出文件一起用
  • streamfilenamehandler互相排斥
  • 若想将日志信息同时输出到文件和控制台,则需要使用Logger处理器增加对应的处理方法。

basicConfig()方法可以实现将日志信息输出到文件中或者输出到定义的输出流中。

  • 输出到文件中,可以通过filename参数,或者通过handler处理器创建相应文件对象实现
    • 内部创建`FileHandle()`对象
  • 打印到输出流中,使用默认参数即可
    • 内部创建`StreamHandler()`对象

其中,format指定输出的格式和内容,format可以输出很多有用信息,具体格式见Formatter类

该方法相当于显示调用basicConfig()进行参数的配置,而直接调用logging.info()接口函数,若没有handler时,是隐式调用basicConfig()的默认参数形式。

1)日志信息打印到输出流

 1import logging
2# 直接调用basicConfig()函数,使用默认参数
3logging.basicConfig()
4logging.debug('This is a debug message')
5logging.info('This is an info message')
6logging.warning('This is a warning message')
7logging.error('This is an error message')
8logging.critical('This is a critical message')
9
10# 输出:(默认级别是WARNING)
11WARNING:root:This is a warning message
12ERROR:root:This is an error message
13CRITICAL:root:This is a critical message

2)格式化输出

格式化输出时间:
通过datefmt参数可以格式化输出log的时间。

常用的输出格式如下:
format = '%(asctime)s - %(filename)s[line:%(lineno)d]- %(levelname)s: %(message)s'
该格式可以输出日志的打印时间,输出模块及行号,日志级别和输出的日志内容。

1import logging
2logging.basicConfig(format='%(asctime)s %(message)s', datefmt = '%Y-%m-%d %I:%M:%S %p')
3logging.warning('is when this event was occurred.')
4
5# 输出结果
62019-10-15 04:03:17 PM is when this event was occurred.

3) 将日志输出到指定文件
指定filenamefilemode参数,可将日志内容输出到指定文件,示例代码如下:

1import logging
2
3logging.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)
4logging.debug('This is a debug message')
5logging.info('This is an info message')
6logging.warning('This is a warning message')
7logging.error('This is an error message')

生成的日志文件test.log,内容如下:

在这里插入图片描述
在这里插入图片描述

4.3 一步步配置Logger

basicConfig函数提供了一种较为简便的使用日志的方式,如果想要获取更加复杂的使用,则需要自己一步步配置来实现。

配置逻辑:

  • 一个logger对象可以添加多个handler对象,通过addHandler()函数
  • 每个handler对象可以有一个Formatter对象来指定格式,通过setFormatter()函数
  • handlerlogger对象都需要设置一个日志等级,通过setLevel()函数
  • 根据需要loggerhandler对象可以添加多个Filter对象,通过addFilter()函数

示例:

 1import logging
2
3logger = logging.getLogger(__name__)
4logger.setLevel(level=logging.INFO)
5
6handler = logging.FileHandler('output.log', mode='a', encoding=None, delay=False)
7formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',
8                                datefmt='%Y-%m-%d %H:%M:%S',
9                                style='%')
10handler.setFormatter(formatter)
11handler.setLevel(logging.DEBUG)
12handler.set_name('output-log')
13
14logger.addHandler(handler)
15
16a=0
17try:
18    res = 100 / a
19except:
20    logger.error('Divisor is equal to 0.', exc_info=True)

将日志同时输出到屏幕和文件

 1import logging
2logger = logging.getLogger(__name__)
3logger.setLevel(level = logging.DEBUG)
4handler = logging.FileHandler("log.txt")
5handler.setLevel(logging.INFO)
6formatter = logging.Formatter('%(asctime)s - %(name)s - %(message)s')
7handler.setFormatter(formatter)
8
9console = logging.StreamHandler()
10console.setLevel(logging.INFO)
11
12logger.addHandler(handler)
13logger.addHandler(console)
14
15logger.info("Start print log")
16logger.debug("Do something")
17logger.warning("Something maybe fail.")
18logger.info("Finish")

log.txt文件和控制台均可看到输出,控制台的输出,其中文件中内容(有格式)如下:

在这里插入图片描述
在这里插入图片描述

通过这段代码可以看出,logging有一个日志处理的主对象(logger=logging.getLogger()),其他的处理方法均是通过addHandler添加进去。

4.4 通过dictConfig配置

通过dictConfig配置方法是通过python代码构建一个dict对象,也可以通过JSONyaml文件来进行配置。
这种方式使用起来更加灵活,强大。我们可以把很多的数据转换成自字典形式,然后将他们填充到一个配置字典中。

具体如何转换可以参考logging.config.py文件。

4.4.1 dictConfig()

该函数可以从一个字典对象中获取日志配置信息,config参数就是这个字典对象。

定义如下:

1def dictConfig(config):
2    """Configure logging using a dictionary."""
3    dictConfigClass(config).configure()

如使用YAML格式的文件类配置(配置内容见下方示例),则使用示例:

 1import logging
2import logging.config
3import yaml
4
5with open('logging.yml''r'as f_config:
6    dict_cfg = yaml.load(f_config)
7logging.config.dictConfig(dict_cfg)
8
9logger = logging.getLogger('console')
10logger.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:(可选),当前handlerformatterid
    • filters:(可选),当前handlerfiltersid列表
  • loggers:字典形式

    • level:(可选),logger的等级
    • propagate:(可选):默认值为1,表示将消息传递给父loggerhandler进行处理。
    • filters:(可选)
    • handlers:(可选) ,当前loggerhandlersid列表
    • qualname:代码中使用logging.getLogger()时,读取的就是qualname这个选项。
  • root:这是root logger的配置项

    • level:日志等级
    • handlers :当前root loggerhandlersid列表

4.4.3 配置示例

 1version:1
2root:
3    level:DEBUG
4    handlers:[filehandler, ]
5loggers:
6    console:
7        level:WARNING
8        handlers:[consolehandler,]
9        propagate:1
10handlers:
11    filehandler:
12        class:logging.FileHandler
13        filename:logs/sys.log
14        level:WARNING
15        formatter:fileformatter
16    consolehandler:
17        class:logging.StreamHandler
18        stream:ext://sys.stdout
19        level:DEBUG
20        formatter:consoleformatter
21formatters:
22    fileformatter:
23        format:'%(asctime)s [%(name)s][%(levelname)s]:%(message)s'
24    consoleformatter:
25        format:'%(asctime)s[%(levelname)s]:%(message)s'

外部对象访问:外部对象是指不能直接进行访问,需要import导入的对象,这时候需要加一个ext://前缀以便识别,然后系统就会import这个前缀后面的内容。

参考资料:
Python日志库logging总结-可能是目前为止将logging库总结的最好的一篇文章
python基础学习十 logging模块详细使用【转载】 - louis_w - 博客园
Python之路(第十七篇)logging模块 - Nicholas- - 博客园