logging模块

Posted kuxingseng95

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了logging模块相关的知识,希望对你有一定的参考价值。

logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比print,具备如下优点:

  1. 多线程支持
  2. 可以通过设置不同的日志等级,在release版本中只输出重要信息,而不必显示大量的调试信息;
  3. print将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging则可以由开发者决定将信息输出到什么地方,以及怎么输出;
  4. logging中可以选择很多消息级别,如debug、info、warning、error以及critical。通过赋予logger或者handler不同的级别,开发者就可以只输出错误信息到特定的记录文件,或者在调试时只记录调试信息。

logging模块的导入

import logging

最简单的logging日志

import logging
logging.debug(debug message)
logging.info(info message)
logging.warning(warning message)
logging.error(error message)
logging.critical(critical message)  

# 输出为:
# WARNING:root:warning message
# ERROR:root:error message
# CRITICAL:root:critical message

从这个例子中,我们可以看到默认情况下

  1. Python的logging模块将日志打印到了标准输出中
  2. 只显示大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET)
  3. 默认的日志格式为日志级别:日志器名称:日志内容。

关于logging的内容远不止这么简单,且慢慢看下去。

在后面分别会说明日志级别,模块级函数记录日志,logging模块日志流处理流程

日志级别

日志级别分类

  • NOTSET:没有设置
  • DEBUG:最详细的日志信息,典型应用场景是在开发过程中打印一些运行信息。
  • INFO:重要信息,信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
  • WARNING:警告信息,表明会出现潜在错误的情形,当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的,有些信息不是错误信息,但是也要给程序员的一些提示。
  • ERROR:错误信息,由于一个更严重的问题导致某些功能不能正常运行时记录的信息,虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
  • CRITICAL:严重错误信息,当发生严重错误,导致应用程序不能继续运行时记录的信息

说明

  • 后面的日志等级是从大到小依次降低的,即:CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,而日志的信息量是依次增多的;
  • 开发应用程序或部署开发环境时,可以使用DEBUG或INFO级别的日志获取尽可能详细的日志信息来进行开发或部署调试;应用上线或部署生产环境时,应该使用WARNING或ERROR或CRITICAL级别的日志来降低机器的I/O压力和提高获取错误日志信息的效率。日志级别的指定通常都是在应用程序的配置文件中进行指定的。
  • 当为某个应用程序指定一个日志级别后,应用程序会记录所有日志级别大于或等于指定日志级别的日志信息,而不是仅仅记录指定级别的日志信息。

logging中记录日志

logging模块中提供了两种记录日志的方式:

  • 第一种方式是使用logging提供的模块级别的函数
  • 第二种方式是使用Logging日志系统的四大组件

说明: logging模块提供的模块级别的那些函数实际上也是通过这几个组件的相关实现类来记录日志的,只是在创建这些类的实例时设置了一些默认值。

一、模块级函数记录日志

常用的记录日志的模块级函数

技术分享图片

上面说到了logging的basicConfig函数进行参数的配置。

logging.basicConfig函数各个参数

  • filename:指定日志文件名
  • filemode:指定日志文件的打开模式,默认为‘a‘。需要注意的是,该选项要在filename指定时才有效
  • format:指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
    • 常用参数及作用
      • %(asctime)s:日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896
        %(created)f:日志事件发生的时间--时间戳,就是当时调用time.time()函数返回的值
        %(relativeCreated)d:日志事件发生的时间相对于logging模块加载时间的相对毫秒数(目前还不知道干嘛用的)
        %(msecs)d:日志事件发生事件的毫秒部分
        %(levelname)s:该日志记录的文字形式的日志级别(‘DEBUG‘, ‘INFO‘, ‘WARNING‘, ‘ERROR‘, ‘CRITICAL‘)
        %(levelno)s:该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
        %(name)s:所使用的日志器名称,默认是‘root‘,因为默认使用的是 rootLogger
        %(message)s:日志记录的文本内容,通过 msg % args计算得到的
        %(pathname)s:调用日志记录函数的源码文件的全路径
        %(filename)s:pathname的文件名部分,包含文件后缀
        %(module)s:filename的名称部分,不包含后缀
        %(lineno)d:调用日志记录函数的源代码所在的行号
        %(funcName)s:调用日志记录函数的函数名
        %(process)d:进程ID
        %(processName)s:进程名称,Python 3.1新增
        %(thread)d:线程ID
        %(thread)s:线程名称

  • datefmt:指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效
  • level:设置日志级别,默认为logging.WARNNING;
  • stream:指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,stream和filename不能同时提供,否则会引发 ValueError异常;
  • style:Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为‘%‘、‘{‘和‘$‘,默认为‘%‘
  • handlers:Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。

 简单的日志输出

在一开始写的那个简单的例子其实就是用模块级函数写的日志。除了上面的那种写法,还有另一种写法。

技术分享图片
import logging
logging.log(logging.DEBUG, "This is a debug log.")
logging.log(logging.INFO, "This is a info log.")
logging.log(logging.WARNING, "This is a warning log.")
logging.log(logging.ERROR, "This is a error log.")
logging.log(logging.CRITICAL, "This is a critical log.")

# 输出为:
# WARNING:root:This is a warning log.
# ERROR:root:This is a error log.
# CRITICAL:root:This is a critical log.
View Code

 正如前面一开始说的那样,日志的的默认日志级别为WARNING,只有它和它以上的日志记录被输出。而默认的输出格式是:日志级别:日志器名称:日志内容。而这样输出也是因为

logging模块提供的日志记录函数所使用的日志器设置的日志格式默认是BASIC_FORMAT,其值为:

"%(levelname)s:%(name)s:%(message)s"

 详细的默认值可以看源码,当我们没有提供任何配置信息的时候,这些函数都会去调用logging.basicConfig(**kwargs)方法,且不会向该方法传递任何参数。继续查看basicConfig()方法的代码就可以找到上面这些问题的答案了。

 稍作配置后的模块级函数日志输出

技术分享图片
import logging
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"

logging.basicConfig(filename=my.log, level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT)

logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")

# 此时会在my.log日志文件中看到下面的输出结果
# 08/12/2018 21:54:39 PM - DEBUG - This is a debug log.
# 08/12/2018 21:54:39 PM - INFO - This is a info log.
# 08/12/2018 21:54:39 PM - WARNING - This is a warning log.
# 08/12/2018 21:54:39 PM - ERROR - This is a error log.
# 08/12/2018 21:54:39 PM - CRITICAL - This is a critical log.
View Code

会上面这些基本的日志记录就算是掌握了。 

说明:

  • logging.basicConfig()函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。
  • 日志器(Logger)是有层级关系的,上面调用的logging模块级别的函数所使用的日志器是RootLogger类的实例,其名称为‘root‘,它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。
  • 如果要记录的日志中包含变量数据,可使用一个格式字符串作为这个事件的描述消息(logging.debug、logging.info等函数的第一个参数),然后将变量数据作为第二个参数*args的值进行传递,如:logging.warning(‘%s is %d years old.‘, ‘Tom‘, 10),输出内容为WARNING:root:Tom is 10 years old.
  • logging.debug(), logging.info()等方法的定义中,除了msg和args参数外,还有一个**kwargs参数。它们支持3个关键字参数: exc_info, stack_info, extra,下面对这几个关键字参数作个说明。
关于exc_info, stack_info, extra关键词参数的说明:
  • exc_info: 其值为布尔值,如果该参数的值设置为True,则会将异常异常信息添加到日志消息中。如果没有异常信息则添加None到日志信息中。
  • stack_info: 其值也为布尔值,默认值为False。如果该参数的值设置为True,栈信息将会被添加到日志信息中。
  • extra: 这是一个字典(dict)参数,它可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突。

一个例子:

技术分享图片
import logging
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(user)s[%(ip)s] - %(message)s"
DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"

logging.basicConfig(format=LOG_FORMAT, datefmt=DATE_FORMAT)
logging.warning("Some one delete the log file.", exc_info=True, stack_info=True, extra={user: Tom, ip:192.168.199.22})

# 打印结果为
# 08/12/2018 22:01:43 PM - WARNING - Tom[192.168.199.22] - Some one delete the log file.
# NoneType: None
# Stack (most recent call last):
#   File "D:/workspace/modue/bin.py", line 6, in <module>
#     logging.warning("Some one delete the log file.", exc_info=True, stack_info=True, extra={‘user‘: ‘Tom‘, ‘ip‘:‘192.168.199.22‘})
View Code

logging模块日志流处理流程

在说明用logging模块的四大组件记录日志之前, 很有必要对logging模块所包含的重要组件以及其工作流程做个全面、简要的介绍,这有助于我们更好的理解我们所写的代码(将会触发什么样的操作)。

 logging模块的四大组件

技术分享图片

logging模块就是通过这些组件来完成日志处理的,上面所使用的logging模块级别的函数也是通过这些组件对应的类来实现的。

这些组件之间的关系描述:

  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
  • 不同的处理器(handler)可以将日志输出到不同的位置;
  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。

 logging日志模块相关类及常用的方法介绍

下面介绍下与logging四大组件相关的类:Logger,Handler,Filter,Formatter。

  Logger类  

Logger对象有3个任务要做

  1. 向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息;
  2. 基于日志严重等级(默认的过滤设施)或filter对象来决定要对哪些日志进行后续处理;
  3. 将日志消息传送给所有感兴趣的日志handlers。

logger对象最常用的方法分为两类:配置方法和消息发送。

创建一个Logger对象

  1. 方法一:创建Logger类的实例
    • logger = logging.Logger
  2. 方法二:通过logging.getLogger([name])方法(常用)
    • logger = logging.getLogger() 
    • logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为‘root‘。若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用,因为在创建日志器的时候是用的单例模式。

当我们创建了一个Logger对象,其实它并没什么功能,需要我们给它配置添加。

常用的配置方法

(下面写的logger都是Logger类的实例化对象):

  • logger.setLevel():设置日志器将会处理的日志消息的最低严重级别,比如:logger.setLevel("DEBUG")
  • logger.addHandler()和logger.removeHandler():为该Logger对象添加 和 移除一个Handler对象
    • import logging
      
      logger = logging.getLogger()    # 获得一个Logger实例化对象
      fh = logging.FileHandler(my_log)  # 创建一个FileHandler对象,用于写入日志文件,参数为日志写到哪个文件中
      ch = logging.StreamHandler()  # 创建一个StreamHandler对象,用于输出到控制台
      
      logger.addHandler(fh)       # 为Logger对象添加FileHandler对象
      logger.addHandler(ch)       # 为Logger对象添加StreamHandler对象
      logger.removeHandler(fh)    # 为Logger对象删除FileHandler对象
      logger.removeHandler(ch)    # 为Logger对象删除StreamHandler对象
  • logger.addFilter()和logger.removeFilter():
    • 使用参照上面例子,但是它只接收FileHandler对象

创建日志记录

  • Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical():创建一个与它们的方法名对应等级的日志记录
  • Logger.exception():创建一个类似于Logger.error()的日志消息
  • Logger.log():需要获取一个明确的日志level参数来创建一个日志记录

例如:

logger = logging.getLogger()    # 获得一个Logger实例化对象
logger.debug(logger debug message)
logger.info(logger info message)
logger.warning(logger warning message)
logger.error(logger error message)
logger.critical(logger critical message)

说明:

  • Logger.exception()与Logger.error()的区别在于:Logger.exception()将会输出堆栈追踪信息,另外通常只是在一个exception handler中调用该方法。
  • Logger.log()与Logger.debug()、Logger.info()等方法相比,虽然需要多传一个level参数,显得不是那么方便,但是当需要记录自定义level的日志时还是需要该方法来完成。

关于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来关闭这种传递机制。

 

  Handler类 

 Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。在上面的Logger类中,我们使用了Handler类的一些知识。所以对Handler类应该有了一定的了解,这里会详细的介绍这个类。

Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:

  1. 把所有日志都发送到一个日志文件中;
  2. 把所有严重级别大于等于error的日志发送到stdout(标准输出);
  3. 把所有严重级别为critical的日志发送到一个email邮件地址。

这种场景就需要3个不同的handlers,每个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‘信息的出现。

handle对象常用配置方法

  • Handler.setLevel():设置handler将会处理的日志消息的最低严重级别
  • Handler.setFormatter():为handler设置一个格式器对象
  • Handler.addFilter() 和 Handler.removeFilter():为handler添加 和 删除一个过滤器对象

  filter类  

Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。该类定义如下:

class logging.Filter(name=‘‘)
    filter(record)

比如,一个filter实例化时传递的name参数值为‘A.B‘,那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:‘A.B‘,‘A.B,C‘,‘A.B.C.D‘,‘A.B.D‘,而名称为‘A.BB‘, ‘B.A.B‘的loggers产生的日志则会被过滤掉。如果name的值为空字符串,则允许所有的日志事件通过过滤。

filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。

  formatter类  

Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个Formatter的子类来完成。

Formatter类的构造方法定义如下:

logging.Formatter.__init__(fmt=None, datefmt=None, style=‘%‘)

可见,该构造方法接收3个可选参数:

  • fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
  • datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
  • style:Python 3.2新增的参数,可取值为 ‘%‘, ‘{‘和 ‘$‘,如果不指定该参数则默认使用‘%‘

logging日志处理流程

下图大致表述了日志流的处理流程

技术分享图片

我们来描述下上面这个图的日志流处理流程:

  • 1)(在用户代码中进行)日志记录函数调用,如:logger.info(...),logger.debug(...)等;
  • 2)判断要记录的日志级别是否满足日志器设置的级别要求(要记录的日志级别要大于或等于日志器设置的级别才算满足要求),如果不满足则该日志记录会被丢弃并终止后续的操作,如果满足则继续下一步操作;
  • 3)根据日志记录函数调用时掺入的参数,创建一个日志记录(LogRecord类)对象;
  • 4)判断日志记录器上设置的过滤器是否拒绝这条日志记录,如果日志记录器上的某个过滤器拒绝,则该日志记录会被丢弃并终止后续的操作,如果日志记录器上设置的过滤器不拒绝这条日志记录或者日志记录器上没有设置过滤器则继续下一步操作--将日志记录分别交给该日志器上添加的各个处理器;
  • 5)判断要记录的日志级别是否满足处理器设置的级别要求(要记录的日志级别要大于或等于该处理器设置的日志级别才算满足要求),如果不满足记录将会被该处理器丢弃并终止后续的操作,如果满足则继续下一步操作;
  • 6)判断该处理器上设置的过滤器是否拒绝这条日志记录,如果该处理器上的某个过滤器拒绝,则该日志记录会被当前处理器丢弃并终止后续的操作,如果当前处理器上设置的过滤器不拒绝这条日志记录或当前处理器上没有设置过滤器测继续下一步操作;
  • 7)如果能到这一步,说明这条日志记录经过了层层关卡允许被输出了,此时当前处理器会根据自身被设置的格式器(如果没有设置则使用默认格式)将这条日志记录进行格式化,最后将格式化后的结果输出到指定位置(文件、网络、类文件的Stream等);
  • 8)如果日志器被设置了多个处理器的话,上面的第5-8步会执行多次;
  • 9)这里才是完整流程的最后一步:判断该日志器输出的日志消息是否需要传递给上一级logger(之前提到过,日志器是有层级关系的)的处理器,如果propagate属性值为1则表示日志消息将会被输出到处理器指定的位置,同时还会被传递给parent日志器的handlers进行处理直到当前日志器的propagate属性为0停止,如果propagate值为0则表示不向parent日志器的handlers传递该消息,到此结束。

可见,一条日志信息要想被最终输出需要依次经过以下几次过滤:

  • 日志器等级过滤;
  • 日志器的过滤器过滤;
  • 日志器的处理器等级过滤;
  • 日志器的处理器的过滤器过滤;

需要说明的是: 

1.关于上面第9个步骤,如果propagate值为1,那么日志消息会直接传递交给上一级logger的handlers进行处理,此时上一级logger的日志等级并不会对该日志消息进行等级过滤。

2.使用getLogger()方法的时候,如果要返回的日志器名字相同,返回的为同一个日志器,因为它的内部是用单例生成的,所以当多个对象使用同一日志器然后进行设置时,新的设置会覆盖旧的设置。而当输出的时候,有几个对象要显示,就显示几遍日志。

3.日志器是一个树形结构,比如:getLogger("mylogger.sontree")mylogger是root的孩子,sontree是mylogger的孩子,root的孙子。

4.当我们使用子层的日志器的时候,它会默认往上去找父级直到找到root,有几层显示几遍日志:

  例如:

技术分享图片
import logging

logger = logging.getLogger()    # 获得一个Logger实例化对象,由于没写参数返回的root日志器
ch = logging.StreamHandler()  # 创建一个StreamHandler对象,用于输出到控制台
logger.addHandler(ch)       # 为Logger对象添加StreamHandler对象
logger.setLevel("ERROR")

logger1 = logging.getLogger("my_log")    # 获得一个名为Logger实例化对象
logger1.addHandler(ch)  # # 为Logger对象添加StreamHandler对象
logger1.setLevel("DEBUG")


logger.debug(logger debug message)
logger.info(logger info message)
logger.warning(logger warning message)
logger.error(logger error message)
logger.critical(logger critical message)

logger1.debug(logger1 debug message)
logger1.info(logger1 info message)
logger1.warning(logger1 warning message)
logger1.error(logger1 error message)
logger1.critical(logger1 critical message)

# 输出结果为:
# logger error message
# logger critical message
# logger1 debug message
# logger1 debug message
# logger1 info message
# logger1 info message
# logger1 warning message
# logger1 warning message
# logger1 error message
# logger1 error message
# logger1 critical message
# logger1 critical message
View Code

例子中,logger不写参数获取的日志器为root,它是正常显示的,而logger1获取的则是名为my_log的日志器,它除了打印自己的,还向上去找,找到了它的父级就是顶级的root,所以也打印了一遍,这样就看到了后面打印了两遍的日志。你可以再按上面说到创建一个my_log日志器的子日志器进行测试。

5.上面提到了会重复打印的问题,那么我不想这样怎么办。那就是把例子中的logger.addHandler(ch)  注释了,不让父日志器的handler工作就行了。所以我们需要注意的就是每当一个日志器的父日志器有输出的时候,它就会重复输出一次。

 用logging模块的四大组件记录日志

现在,我们对logging模块的重要组件及整个日志流处理流程都应该有了一个比较全面的了解,下面我们来看一个例子。

需求

现在有以下几个日志记录的需求:

  • 1)要求将所有级别的所有日志都写入磁盘文件中
  • 2)all.log文件中记录所有的日志信息,日志格式为:日期和时间 - 日志级别 - 日志信息
  • 3)error.log文件中单独记录error及以上级别的日志信息,日志格式为:日期和时间 - 日志级别 - 文件名[:行号] - 日志信息
  • 4)要求all.log在每天凌晨进行日志切割

分析

  • 1)要记录所有级别的日志,因此日志器的有效level需要设置为最低级别--DEBUG;
  • 2)日志需要被发送到两个不同的目的地,因此需要为日志器设置两个handler;另外,两个目的地都是磁盘文件,因此这两个handler都是与FileHandler相关的;
  • 3)all.log要求按照时间进行日志切割,因此他需要用logging.handlers.TimedRotatingFileHandler; 而error.log没有要求日志切割,因此可以使用FileHandler;
  • 4)两个日志文件的格式不同,因此需要对这两个handler分别设置格式器;

代码实现

import logging
import logging.handlers
import datetime

logger = logging.getLogger(mylogger)
logger.setLevel(logging.DEBUG)

rf_handler = logging.handlers.TimedRotatingFileHandler(all.log, when=midnight, interval=1, backupCount=7, atTime=datetime.time(0, 0, 0, 0))
rf_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))

f_handler = logging.FileHandler(error.log)
f_handler.setLevel(logging.ERROR)
f_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))

logger.addHandler(rf_handler)
logger.addHandler(f_handler)

logger.debug(debug message)
logger.info(info message)
logger.warning(warning message)
logger.error(error message)
logger.critical(critical message)

all.log文件输出

2018-08-13 11:00:58,246 - DEBUG - debug message
2018-08-13 11:00:58,247 - INFO - info message
2018-08-13 11:00:58,247 - WARNING - warning message
2018-08-13 11:00:58,247 - ERROR - error message
2018-08-13 11:00:58,247 - CRITICAL - critical message

error.log文件输出

2018-08-13 11:00:58,247 - ERROR - bin.py[:21] - error message
2018-08-13 11:00:58,247 - CRITICAL - bin.py[:22] - critical message

 
























以上是关于logging模块的主要内容,如果未能解决你的问题,请参考以下文章

如何使用模块化代码片段中的LeakCanary检测内存泄漏?

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印出C++的函数耗时以及代码片段耗时详情

python常用代码片段总结

前端开发常用js代码片段

vs code 自定义代码片段

如何有条件地将 C 代码片段编译到我的 Perl 模块?