Python中Logging 日志模块

一、基础知识

Logging库是非常常用的记录日志库,通过logging模块存储各种格式的日志,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等

Logging优点:

  • 1.你可以控制消息的级别,过滤掉那些并不重要的消息。
  • 2.你可决定输出到什么地方,以及怎么输出。有许多的重要性别级可供选择,debug、info、warning、error 以及 critical。通过赋予 logger 或者 handler 不同的级别,你就可以只输出错误消息到特定的记录文件中,或者在调试时只记录调试信息。

for example:

import logging

# 1、创建一个logger
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)

# 2、创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log')
fh.setLevel(logging.DEBUG)

# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# 3、定义handler的输出格式(formatter)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 4、给handler添加formatter
fh.setFormatter(formatter)
ch.setFormatter(formatter)

# 5、给logger添加handler
logger.addHandler(fh)
logger.addHandler(ch)

上面代码的用到了getLogger()、setLevel()、setFormatter()、StreamHandler()。后续进阶部分,我们会重点讲解这些函数。如果想系统了解python的日志写入,又不想趴英文网站,可以继续往下看,绝对详细、好理解。

根据它们用来跟踪的事件的级别或事件的严重程度命名的。以下描述了标准水平及其适用性(按严重程度的增加顺序)

可以看到,严重程度的级别依次是DEBUG<INFO<WARNING<ERROR<CRITICAL

1.1 简单栗子

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

1.2 将日志写入到一个文件中

import logging
import os
os.chdir("./") # 日志写入地址
logging.basicConfig(filename='example.log', level=logging.DEBUG) 
# 注意:上面level设置的是显示的最低严重级别,小于level设置的最低严重级别将不会打印出来
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
logging.error('And non-ASCII stuff, too, like Øresund and Malmö')

basicConfig()函数要在debug()、info()等运行,且只要第一次运行才生效。后续的调用都是无效的。上述logging.basicConfig()写入的日志是增量的写入。如果想要覆盖之前的日志,可以设定为:

logging.basicConfig(filename='example.log',filemode='w',level=logging.DEBUG)

2. 多个模块日志调用

logger.py

import logging
import mylib

def main():
    logging.basicConfig(filename='myapp.log', level=logging.INFO)
    logging.info('Started')
    mylib.do_something() # 这里打印的是另一个模块的日志
    logging.info('Finished')

if __name__ == '__main__':
    main()

mylib.py

import logging

def do_something():
    logging.info("Doing things")

输出结果(下面打印结果中含有root,后续我们会说到怎样避免打印root)

INFO:root:Started

INFO:root:Doing something

INFO:root:Finished

1.3. 改变呈现message的format(格式)

:定义日志呈现中预想的日志呈现格式

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

输出结果:

DEBUG:This message should appear on the console

INFO:So should this

WARNING:And this, too

上面日志打印的结果格式中,少了root:。 有一系列可以用做格式化的属性,如下:

Attribute name Format Description
args You shouldn’t need to format this yourself. The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when there is only one argument, and it is a dictionary).
asctime %(asctime)s Human-readable time when the LogRecord was created. By default this is of the form ‘2003-07-08 16:49:45,896’ (the numbers after the comma are millisecond portion of the time).
created %(created)f Time when the LogRecord was created (as returned by time.time()).
exc_info You shouldn’t need to format this yourself. Exception tuple (à la sys.exc_info) or, if no exception has occurred, None.
filename %(filename)s Filename portion of pathname.
funcName %(funcName)s Name of function containing the logging call.
levelname %(levelname)s Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
levelno %(levelno)s Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL).
lineno %(lineno)d Source line number where the logging call was issued (if available).
message %(message)s The logged message, computed as msg % args. This is set when Formatter.format() is invoked.
module %(module)s Module (name portion of filename).
msecs %(msecs)d Millisecond portion of the time when the LogRecord was created.
msg You shouldn’t need to format this yourself. The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object (see Using arbitrary objects as messages).
name %(name)s Name of the logger used to log the call.
pathname %(pathname)s Full pathname of the source file where the logging call was issued (if available).
process %(process)d Process ID (if available).
processName %(processName)s Process name (if available).
relativeCreated %(relativeCreated)d Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
stack_info You shouldn’t need to format this yourself. Stack frame information (where available) from the bottom of the stack in the current thread, up to and including the stack frame of the logging call which resulted in the creation of this record.
thread %(thread)d Thread ID (if available).
threadName %(threadName)s Thread name (if available)

比如,我们将上面logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) 修改为 logging.basicConfig(format='%(levelname)s:%(message)s:%(module)s', level=logging.DEBUG)。

输出的结果将会变为:

DEBUG:This message should appear on the console:logger

INFO:So should this:logger

WARNING:And this, too:logger

如果你想加入时间,可以试试加上%(asctime)s。

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

输出:

2010-12-12 11:41:42,612 is when this event was logged.

如果你希望控制输出时间的格式,可以使用datefmt.

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

输出:

12/12/2010 11:46:36 AM is when this event was logged.

二、进阶知识

logging库提供了模块化的方法和几个组件,下列列出了模块定义的基础类和函数

Loggers :记录器公开应用程序代码直接使用的接口。 Handlers :处理程序将日志记录(由记录器创建)发送到相应的目标。 Filters :过滤器提供了更细粒度的工具,用于确定要输出哪些日志记录。 Formatters: 格式化程序指定最终输出中日志记录的布局。

logger = logging.getLogger(__name__)

这意味着logger的名称和包/模块层次结构一致,从logger 的名称记录事件是非常直观的方法。logger的根层级调用根logger. 根logger是函数debug()、info()、warning()、error()和critical()使用的记录器,这些函数只调用根记录器的同名方法。根logger名称会在打印时以'root'出现在输出结果中。

当然,可以将消息记录到不同的目的地。该软件包支持将日志消息写入文件、HTTP GET/POST位置、通过SMTP发送的电子邮件、通用套接字、队列或特定于操作系统的日志机制,如syslog或Windows NT事件日志。目的地由handler classes提供服务。如果有任何内置handler classes都无法满足的特殊要求,则可以创建自己的日志目标类。

默认情况下,不会为任何日志消息设置目标。可以使用basicConfig()指定目标(如控制台或文件),如上述所示。如果调用函数debug()、info()、warning()、error()和critical(),它们将检查是否未设置目标位置(如控制台或文件);如果没有设置,他们将设置控制台的目的地(sys.stderr)和显示消息的默认格式,然后再委托root logger进行实际的消息输出。

basicConfig()设定的默认形式为:severity:logger name:message

2.1 Logger对象

Logger对象有三方面的工作。

  1. logger对象向应用程序代码公开了几种方法,以便应用程序可以在运行时记录消息。
  2. logger对象根据severity ,即严重性(默认过滤功能)或过滤对象确定要对哪些日志消息采取行动。
  3. logger对象对象将相关日志消息传递给所有感兴趣的日志处理程序。

在Logger对象中最常用的方方有两类:配置和消息发送。

最常用的配置方法:

  • Logger.setLevel() 指定处理的最低严重性日志消息,其中debug是最低的内置严重性级别,critical是最高的内置严重性级别。例如,如果严重性级别为INFO,Logger将只处理 INFO, WARNING, ERROR和CRITICAL messages,并将忽略DEBUG 消息。

举个栗子,比如我们在logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)。这里的Level设置的是最低严重性级别DEBUG,那么,将打印所有的信息,包括DEBUG ,INFO,WARNING, ERROR和CRITICAL ;但是当上面的basicConfig,则打印当中没有DEBUG,只有INFO,WARNING, ERROR和CRITICAL。这是因为level设置的最低级别是INFO,而DEBUG的严重性级别最低,所有在此不用打印。

  • Logger.addHandler()和 Logger.removeHandler() 从Logger对象中添加和移出handler对象
  • Logger.addFilter()和 Logger.removeFilter()从Logger对象中添加和移出filter对象

配置logger对象后,以下方法将创建日志消息:

  • Logger.debug(), Logger.info(), Logger.warning(),Logger.error()和Logger.critical()都会创建日志记录,其中包含一条message和一个对应于各自方法名称的级别,message实际上是一个格式字符串,它可能包含%s、%d、%f等标准字符串替换语法。其余参数是与message中的替换字段相对应的对象列表。
  • Logger.exception()创建了一个和Logger.error()相似的message。 不同的是Logger.exception()伴随的是一堆轨迹,只能从exception handler中调用此方法。
  • Logger.log()将日志级别作为显式参数。与使用上面列出的日志级别便利方法相比,记录消息要详细一些,但这是如何使用自定义方法对日志进行记录。
  • getLogger() 返回对具有指定名称的Logger实例的引用(如果提供了),或者如果没有指定,则返回对根实例的引用。这些名称是以句点分隔的层次结构。使用相同名称多次调用getLogger()将返回对同一记录器对象的引用。在层级列表中较低的记录器是列表中较高记录器的子级。例如,给定一个名为foo的Logger, 那么,foo.bar, foo.bar.baz, and foo.bam 皆是foo的后代

2.2 Handle对象

Handler对象负责将适当的日志消息(基于日志消息的严重性)分派到处理程序的指定目标。Handler对象可以使用addHandler()方法将零个或多个handler对象添加。应用程序可能希望将所有日志消息发送到日志文件,将所有错误或更高级别的日志消息发送到标准输出,并将所有关键消息发送到电子邮件地址。此场景需要三个单独的处理程序,每个处理程序负责将特定严重性的消息发送到特定位置。

Handler方法有很多,下面主要介绍两种:StreamHandle和FileHandle

回到文章最开始的代码,我们看到

fh=logging.FileHandler('test.log') # 将日志写入到test.log文件 
fh.setLevel(logging.DEBUG) # 并且需要指定写入的内容严重级别

同理,

ch=logging.StreamHandler() # 将日志写入控制台
ch.setLevel(loggong.DEBUG) # 并且需要指定写入的内容严重级别

除此之外,我们会发现,文中最开始的栗子exam.py中在最开始创建Logger时设置了setLevel

logger=logging.getLogger('mylogger') # 创建logger
logger.setLevel(logging.DEBUG) # 写入内容的严重级别

那么为什么后续在定义Handler时又做了一次set Level操作呢?原因是:Logger中设置的级别决定它将传递给Handler的消息严重性。每个Handler设置的setLevel()决定了该处理程序将发送哪些消息(记住:日志中消息是分严重程度的,当确定严重级别是某个层级时,该层级以下的消息不被发送或者记录,该层级以上的消息才被发送或者记录)。

  • setLevel()方法,就像在logger对象中一样,指定将被分派到相应目标的最低严重性。为什么有两个setLevel()方法?logger中设置的级别决定它将传递给其Handler的消息的严重性。每个Handler设置的级别决定了该处理程序将发送哪些消息。
  • setFormatter()选择此处理程序Handler要使用的格式化程序对象.
  • addFilter()和removeFilter()分别在处理程序上配置和取消配置筛选器对象。

到此,我们再次回顾一下exam.py中的代码,相信看到这里大家就明白了。

import logging

# 1、创建一个logger
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)

# 2、创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log')
fh.setLevel(logging.DEBUG)

# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# 3、定义handler的输出格式(formatter)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 4、给handler添加formatter
fh.setFormatter(formatter)
ch.setFormatter(formatter)

# 5、给logger添加handler
logger.addHandler(fh)
logger.addHandler(ch)

2.3 Formatters对象

Formatters对象配置了最终的顺序、结构和日志消息内容。

logging.Formatter.init(fmt=None, datefmt=None, style='%')

fmt:消息格式

datefmt:时间格式,默认为:%Y-%m-%d %H:%M:%S

2.4 Configuring Logging

程序配置日志有三种方式:

  • 调用上述配置方法的Python代码显式创建Loggers、handlers和formatters
  • 创建日志配置文件并使用fileConfig()函数读取它
  • 创建配置信息字典并将其传递给dictConfig()函数

*我们再举个简单的例子说明第一种方式,显示的创建Loggers、handlers和formatters*

import logging
# 创建logger
logger = logging.getLogger('simple_example') # logger名称
logger.setLevel(logging.DEBUG) # 设定logger显示的严重级别

# 创建一个handler,用于输出控制台,并且设定严重级别
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# 创建handler的输出格式(formatter)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将formatter添加到handler中
ch.setFormatter(formatter)

# 将handler添加到logger中
logger.addHandler(ch)

# 输出以下内容
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

$ python simple_logging_module.py

#输出结果如下

2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message

2005-03-19 15:10:26,620 - simple_example - INFO - info message

2005-03-19 15:10:26,695 - simple_example - WARNING - warn message

2005-03-19 15:10:26,697 - simple_example - ERROR - error message

2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

*第二种方式:引入配置文件,配置文件写在logging.conf中*

import logging
import logging.config
logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

配置文件的内容如下:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

$ python simple_logging_module.py

#输出结果如下

2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message

2005-03-19 15:10:26,620 - simple_example - INFO - info message

2005-03-19 15:10:26,695 - simple_example - WARNING - warn message

2005-03-19 15:10:26,697 - simple_example - ERROR - error message

2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

配置文件的好处是:1.配置文件和代码的分离;2.非代码人员也能轻松定义配置文件的内容

*第三种方式:dictConfig()*

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

这种方式是我个人感觉非常非常赞的方式,即实现了代码和配置的分离,又简单好理解。

最后,总结一下整个操作流(官网上down下来的):

w