python logging模块

参考:
python中logging模块上篇
Python Logging 模块完全解读
django日志logging的配置以及处理
python Logging日志记录模块详解

一、简介

1、简介

Python 中的 logging 模块可以让你跟踪代码运行时的事件,当程序崩溃时可以查看日志并且发现是什么引发了错误。Log 信息有内置的层级——调试(debugging)、信息(informational)、警告(warnings)、错误(error)和严重错误(critical)。你也可以在 logging 中包含 traceback 信息。不管是小项目还是大项目,都推荐在 Python 程序中使用 logging。

2、为什么使用 logging?

当你运行一个 Python 脚本时,你可能想要知道脚本的哪个部分在执行,并且检视变量的当前值。

通常,可以只使用print()打印出你想要的信息。在小程序中,可能靠这个就足够了。

但问题是,当你处理有很多个模块的大项目时,就需要一个更加灵活的方法。

因为代码需要经历开发、调试、审查、测试或者上线等不同阶段。在开发时你想要打印的信息类型可能和上线后你想看到的信息类型完全不同。

也就是说,在“测试”时,你可能只想看警告和错误信息,然而在“调试”时,你可能还想看到跟调试相关的信息。

如果你还想打印出使用的模块以及代码运行的时间,那么你的代码很容易变得混乱。

使用logging模块,这些问题就能很容易地解决。

3、logging模块可以

  • 控制信息层级,仅记录需要的信息。

  • 控制显示或者保存日志的时机。

  • 使用内置信息模板控制日志格式。

  • 知晓信息来自于哪个模块。

二、最基本的 logging 例子

1、示例

logging模块是 Python 的标准库,要使用 logging,只需要使用logging.basicConfig()进行基本设置。事实上,这也是可选的。然后就可以调用logging.{level}(message)在控制台中显示信息。

import logging
logging.basicConfig(level=logging.INFO)
logging.info('this is a info')

执行结果
在这里插入图片描述
打印出的日志信息遵循默认格式

日志级别:日志器名称:日志内容
{level}:{logger}:{message}

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

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

上面的例子中,level就是INFO,因为调用的是logging.info()。logger为root,因为这是默认 logger。输出的信息message就是我传递给logging.info()的字符串。

2、默认 logger 是root,其默认的 basicConfig 级别是WARNING

上面例子中,如果不设置logging.basicConfig(level=logging.INFO)的话,日志信息不会被打印出来,默认 logger 是root,其默认的 basicConfig 级别是WARNING。也就是说,只有来自logging.warning或者更高级别的信息才会被记录下来。因此,logging.info()中的信息不会被打印出来。这也是为什么 basicConfig 被设为INFO。

测试如下

import logging

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.")

执行结果为
在这里插入图片描述
默认 logger 是root,其默认的 basicConfig 级别是WARNING,因此只有WARNING级别的日志记录以及大于它的ERROR和CRITICAL级别的日志记录被输出了,而小于它的DEBUG和INFO级别的日志记录被丢弃了。

3、说明

  • logging.basicConfig()函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。

三、logging模块的日志级别

1、日志级别

级别描述
CRITICAL/FATAL50关键错误/消息
ERROR40错误
WARNING30警告消息
INFO20通知消息
DEBUG10调试
NOTSET0无级别

如果设置日志级别为WARNING,则通过日志记录器则输入WARNING级别以下的信息是不会被处理的。

2、使用情况

开发应用程序或部署开发环境时,可以使用DEBUG或INFO级别的日志获取尽可能详细的日志信息来进行开发或部署调试;应用上线或部署生产环境时,应该使用WARNING或ERROR或CRITICAL级别的日志来降低机器的I/O压力和提高获取错误日志信息的效率。日志级别的指定通常都是在应用程序的配置文件中进行指定的。

3、说明:

  • 日志等级:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日志的信息量是依次减少的;
  • 当为某个应用程序指定一个日志级别后,应用程序会记录所有日志级别大于或等于指定日志级别的日志信息,而不是仅仅记录指定级别的日志信息,nginx、php等应用程序以及这里要提高的python的logging模块都是这样的。同样,logging模块也可以指定日志记录器的日志级别,只有级别大于或等于该指定日志级别的日志记录才会被输出,小于该等级的日志记录将会被丢弃。

四、logging 日志格式

1、logging模块中定义好的可以用于format格式字符串中字段解释

字段/属性名称使用格式描述
asctime%(asctime)s日志事件发生的时间–人类可读时间,如:2003-07-08 16:49:45,896
created%(created)f日志事件发生的时间–时间戳,就是当时调用time.time()函数返回的值
relativeCreated%(relativeCreated)d日志事件发生的时间相对于logging模块加载时间的相对毫秒数(目前还不知道干嘛用的)
msecs%(msecs)d日志事件发生事件的毫秒部分
levelname%(levelname)s该日志记录的文字形式的日志级别(‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’)
levelno%(levelno)s该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
name%(name)s所使用的日志器名称,默认是’root’,因为默认使用的是 rootLogger
message%(message)s日志记录的文本内容,通过 msg % args计算得到的
pathname%(pathname)s调用日志记录函数的源码文件的全路径
filename%(filename)spathname的文件名部分,包含文件后缀
module%(module)sfilename的名称部分,不包含后缀
lineno%(lineno)d调用日志记录函数的源代码所在的行号
funcName%(funcName)s调用日志记录函数的函数名
process%(process)d进程ID
processName%(processName)s进程名称,Python 3.1新增
thread%(thread)d线程ID
threadName%(thread)s线程名称

2、自定义日志格式示例

import logging

#设置日志格式
logging.basicConfig(level=logging.INFO,
                   format='%(asctime)s   %(levelname)s   %(message)s')
logging.info('this is a info')

执行结果为
在这里插入图片描述

3、datefmt对时间格式进行修改

例如这里将年月日中间改为 /

import logging
logging.basicConfig(level=logging.INFO,
                   format='%(asctime)s  %(message)s',
                   datefmt='%Y/%m/%d %H:%M:%S')
logging.info('this is a info')

执行结果
在这里插入图片描述

五、创建logger实例和自定义日志器名称

import logging
logging.basicConfig(level=logging.INFO,
                   format='%(name)s  %(levelname)s  %(asctime)s  %(message)s')
logger = logging.getLogger('mylogger')
logger.info('this is a info')

执行结果
在这里插入图片描述

通过getLogger指定logger的名称,名称可以在format中用%(name)s格式化输出。

指定logger名称有一个好处是,在同一个程序的不同文件中引用相同名称的logger对应的是同一个实例,这有利于logging的跨文件调用。

六、logging模块日志流处理流程

1、logging日志模块四大组件

在介绍logging模块的日志流处理流程之前,我们先来介绍下logging模块的四大组件:

组件名称对应类名功能描述
日志器Logger提供了应用程序可一直使用的接口
处理器Handler将logger创建的日志记录发送到合适的目的输出
过滤器Filter提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
格式器Formatter决定日志记录的最终输出格式

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

2、这些组件之间的关系描述

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

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

3、Logger类

(1)Logger对象有3个任务要做:

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

(2)最常用的配置方法如下:

方法描述
Logger.setLevel()设置日志器将会处理的日志消息的最低严重级别
Logger.addHandler() 和 Logger.removeHandler()为该logger对象添加 和 移除一个handler对象
Logger.addFilter() 和 Logger.removeFilter()为该logger对象添加 和 移除一个filter对象

(3)关于Logger.setLevel()方法的说明:

内建等级中,级别最低的是DEBUG,级别最高的是CRITICAL。例如setLevel(logging.INFO),此时函数参数为INFO,那么该logger将只会处理INFO、WARNING、ERROR和CRITICAL级别的日志,而DEBUG级别的消息将会被忽略/丢弃。

(4)logger对象配置完成后,可以使用下面的方法来创建日志记录:

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

说明:

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

(5)那么,怎样得到一个Logger对象呢?

  • 一种方式是通过Logger类的实例化方法创建一个Logger类的实例
  • 但是我们通常都是用第二种方式: logging.getLogger()方法。

logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为’root’。若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。

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

4、Handler类

(1)Handler对象的作用

Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:

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

这种场景就需要3个不同的handlers,每个handler复杂发送一个特定严重级别的日志到一个特定的位置。

(2)Handler方法

一个handler中只有非常少数的方法是需要应用开发人员去关心的。对于使用内建handler对象的应用开发人员来说,似乎唯一相关的handler方法就是下面这几个配置方法:

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

需要说明的是,应用程序代码不应该直接实例化和使用Handler实例。因为Handler是一个基类,它只定义了素有handlers都应该有的接口,同时提供了一些子类可以直接使用或覆盖的默认行为。下面是一些常用的Handler,可以使用下面的方法来创建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’信息的出现。

(3)logging.hanlders.TimedRotatingFileHandler 按日期切割日志

PS:这种方法我在windows环境下django中试了不是好用,可能会出现PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问这样的报错。因为Django默认启用两个进程,一个进程用来检测文件变化,另一个进程是正经的服务器进程。settting.py这个文件被加载了两次,也就是日志文件打开了两次,如果是服务器进程先启动,则看不出毛病来;如果是监控进程首先打开了从settings.py加载了日志,那么正经服务器进程就无法再次加载日志了。 参考:https://www.qedev.com/python/3333.html

如果你的Django项目是单进程的,那么在启动Django项目的时候使用–noreload就可以解决

python manage.py runserver 0.0.0.0:80 --noreload

noreload表示不启动Django的监控进程,也就是说项目代码的改变不再会影响已经载入内存中的代码,这样,Django就只会起一个进程。

如果是多进程的,或者在某些单独的模块,比如celery中为了使用Django的ORM而使用了django.setup()
对于TimedRotatingFileHandler没找到很好的解决方法。。

TimedRotatingFileHandler 可以理解为是一种可设置固定时间间隔的日志记录类,它被集成在 logging 中,直接调用进行实例化和配置就可以使用

TimedRotatingFileHandler 的构造函数为:TimedRotatingFileHandler( filename [, when [, interval [, backupCount] ] ] )

参数解释:

  • filename 是输出日志的文件名称前缀,比如说 testServiceLog 这样的就是日志文件名前缀

  • when 是一个字符串,定义了日志切分的间隔时间单位,这是一个枚举类,可选参数如下:

 "S":Second 秒
 "M":Minutes 分钟
 "H":Hour 小时
 "D":Days 天
 "W":Week day(0 = Monday) (interval==0时代表星期一)
 "midnight":Roll over at midnight 每天的凌晨
  • interval 是间隔时间单位的个数,指等待多少个 when 的时间后 Logger 会自动重建新闻继续进行日志记录,比如:when=’D’,interval=1,表示每天产生一个日志文件;这里需要注意的一点是,如果创建的文件和已有文件存在重名的情况,则会对历史的文件进行覆盖操作,所以使用好 suffix 避免文件名称重复
  • backupCount 是保留日志的文件个数,默认的参数是0,这种设置下是不会自动删除文件的。如果设置为 N(正整数),则会在创建新的日志文件时会检查日志文件个数是否到达 N,达到了的话就会从最先创建的开始删除,从而达到维持日志文件个数为 N 个的目标

代码例子如下:
每天生成一个日志文件,保留最近7天的日志文件,会根据日期来生成文件名称

log_file_handler = TimedRotatingFileHandler(filename=logpath, when="D", interval=1, backupCount=7)

(4)logging.handlers.RotatingFileHandler 按日志文件大小切割

代码示例

log_file_handler = TimedRotatingFileHandler(filename=logpath,maxBytes=1024*1024, backupCount=5)

举个例子,如果backupCount=5,log file定义的名字为app.log,你会得到app.log, app.log.1, app.log.2 一直到 app.log.5。

然而被写入日志的永远是app.log,写满了之后重命名为app.log.1,如果app.log.1存在,app.log.1会先被重名名为app.log.2,依此类推。

PS:同样会遇到多进程使用文件的问题

logging.handlers.RotatingFileHandler这种方法在django中也不好用,也会出现PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问这个错误,可以跟上面一样启动django时加上--noreload

python manage.py runserver 0.0.0.0:80 --noreload

但也可以改为使用concurrent_log_handler.ConcurrentRotatingFileHandler,这个处理器对多进程的时候同样管用。

参考:https://www.codeleading.com/article/77632670363/
https://www.qedev.com/python/3333.html

例子如下

from logging import getLogger, INFO
from concurrent_log_handler import ConcurrentRotatingFileHandler
import os

log = getLogger()
# Use an absolute path to prevent file rotation trouble.
logfile = os.path.abspath("mylogfile.log")
# Rotate log after reaching 512K, keep 5 old copies.
rotateHandler = ConcurrentRotatingFileHandler(logfile, "a", 512*1024, 5)
log.addHandler(rotateHandler)
log.setLevel(INFO)

log.info("Here is a very exciting log message, just for you")

5、Formater类

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新增的参数,可取值为 ‘%’, ‘{‘和 ‘$’,如果不指定该参数则默认使用’%’

设置Formatter主要包括两种方式,一种是通过Formatter类构建Formatter实例,并将其绑定到特定的handler上;一种是通过logging.basicConfig设置

import loging
import time

# 1
logging.basicConfig(
                    format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(funcName)s - %(message)s',
                    datefmt='%Y/%m/%d %H:%M:%S',
                    style='%'  # '%', ‘{‘ or ‘$’
                    )
# 2
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                              datefmt='%Y/%m/%d %H:%M:%S',
                              style='%')
formatter.converter = time.localtime()
formatter.converter = time.gmtime()

6、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表示可以通过过滤。

logging标准库只提供了一个base Filter,其构造函数__init__接收name参数,默认的行为是名为name的logger极其子logger的消息能通过过滤,其余皆会被滤掉。当然我们也可以根据具体的业务逻辑自定义Filter,并且也非常简单,只需要继承logging.Filter类重写filter方法即可,filter方法接收record对象作为参数,返回True代表通过过滤,返回False表示该record被过滤

import logging

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.set_name('output-log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                              datefmt='%Y/%m/%d %H:%M:%S',
                              style='%')


class MyFilter(logging.Filter):
    def filter(self, record):
        if 'result' in record.msg:
            return False
        return True


handler.setFormatter(formatter)
handler.addFilter(MyFilter('aa'))
logger.addHandler(handler)

try:
    result = 10 / 0
except:
    logger.error('Faild to get result')
    logger.error('Faild')

# 输出: 2019/07/13 20:28:51 - __main__ - ERROR - Faild

从python3.2起,自定义filter也可以不继承logging.Filter,只需要定义一个函数并同样绑定即可:

def _filter(record):
    if 'result' in record.msg:
        return False
    return True


handler.addFilter(_filter)

说明:

  • 如果有需要,也可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。
  • 我们还可以通过filter做一些统计工作,比如可以计算下被一个特殊的logger或handler所处理的record数量等。

7、日志流处理流程

在这里插入图片描述

  • 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传递该消息,到此结束。

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

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

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

七、使用logging四大组件记录日志

1、需求

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

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

2、分析

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

3、代码

import logging
import logging.handlers
import datetime

#实例化一个日志器对象
logger = logging.getLogger('mylogger')
#设置日志器处理日志的最低基本为DEBUG
logger.setLevel(logging.DEBUG)

# 是否传播这个日志到祖先logger, 如果设置了False 就不会传到root logger(祖先Logger)的
# 默认StreamHandler那里, 也就是不会打印在页面上
logger.propagate = False

#创建一个TimedRotatingFileHandler类型的handler处理器,因为需要按照时间进行日志切割,设置写入到all.log这个文件
rf_handler = logging.handlers.TimedRotatingFileHandler('all.log', when='midnight', interval=1, backupCount=7,
                                                       atTime=datetime.time(0, 0, 0, 0))
#实例化一个格式器对象
rf_formatter=logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
#为handler处理器设置一个格式器对象
rf_handler.setFormatter(rf_formatter)


#创建一个FileHandler类型的handler处理器,设置写入到error.log这个文件
f_handler = logging.FileHandler('error.log')
#为hanlder设置处理的日志消息的最低严重级别为ERROR
f_handler.setLevel(logging.ERROR)
#实例化一个格式器对象
rf_formatter=logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s")
#为handler处理器设置一个格式器对象
f_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))


#为该logger对象添加handler处理器对象
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文件输出

2020-07-15 15:21:02,086 - DEBUG - debug message
2020-07-15 15:21:02,086 - INFO - info message
2020-07-15 15:21:02,086 - WARNING - warning message
2020-07-15 15:21:02,086 - ERROR - error message
2020-07-15 15:21:02,086 - CRITICAL - critical message

error.log文件输出

2020-07-15 15:21:02,086 - ERROR - loggingtest.py[:62] - error message
2020-07-15 15:21:02,086 - CRITICAL - loggingtest.py[:63] - critical message

八、logging config的几种类型

1、basicConfig

使用logging模块的接口函数basicConfig可以非常方便的进行基本的配置。其中需要注意两点

  • 该函数stream 、filename以及handlers这三个参数是互斥的。
  • logging.basicConfig函数是一个one-off simple configuration facility,只有第一次调用会有效,并且它的调用应该在任何日志记录事件之前。
import logging


logging.basicConfig(
                    filename='./log.log',
                    filemode='w',
                    format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(funcName)s - %(message)s',
                    datefmt='%Y/%m/%d %H:%M:%S',
                    style='%',
                    level=logging.DEBUG
                    )
logger = logging.getLogger(__name__)

try:
    result = 10 / 0
except:
    logger.error('Faild to get result', exc_info=True)

logger.log(50, 'logging critical test')

2、stepByStepConfig

这种设计方式条理清晰,但是会但麻烦一点点,配置的逻辑就是:一个logger可以有多个handler;每个handler可以有一个Formatterhandlerlogger都需要设置一个log Level;根据需要loggerhandler都可以添加多个Filter。

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)
try:
    result = 10 / 0
except:
    logger.error('Faild to get result', exc_info=True)

3、fileConfig

通过配置文件来配置logging模块,这是web应用中比较常见的一种设置方式

fileConfig文件的解读主要基于configparser,它必须包含 [loggers], [handlers] 和 [formatters] section。这是一个比较老的API,不支持配置Filter,估计后面也不会更新了,所以建议大家使用dictConfig。

import logging
import logging.config

logging.config.fileConfig('logging.conf', disable_existing_loggers=True)

# 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  # can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL or NOTSET
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
datefmt=

4、dictConfig

通过dictConfig配置即可以通过python代码构建一个dict对象,也可以通过yaml、JSON文件来进行配置。字典中必须传入的参数是version,而且目前有效的值也只有1。其他可选的参数包括以下:

  • formatters
    对于formatter,主要搜寻format和datefmt参数,用来构造Formatter实例。

  • filters
    对于filter,主要搜寻name参数(默认为空字符串),用来构造Formatter实例。

  • handlers
    对于handlers,主要包括以下参数,其他的参数将作为关键字参数传递到handlers的构造函数:
    class (mandatory). This is the fully qualified name of the handler class.
    level (optional). The level of the handler.
    formatter (optional). The id of the formatter for this handler.
    filters (optional). A list of ids of the filters for this handler.

handlers:
  console:
    class : logging.StreamHandler
    formatter: brief
    level   : INFO
    filters: [allow_foo]
    stream  : ext://sys.stdout
  file:
    class : logging.handlers.RotatingFileHandler
    formatter: precise
    filename: logconfig.log
    maxBytes: 1024
    backupCount: 3

  • loggers

对于loggers,主要包括以下参数:

level (optional). The level of the logger.
propagate (optional). The propagation setting of the logger.
filters (optional). A list of ids of the filters for this logger.
handlers (optional). A list of ids of the handlers for this logger.

  • root

这是给root logger的配置项

  • incremental

是否将此配置文件解释为现有配置的增量, 默认为False

  • disable_existing_loggers

是否要禁用现有的非 root logger,默认为True

以下给出一个较为完成的YAML示例,注意体会loggers, handlers, formatters, filters之间的关联性,该配置文件定义了brief和simple两种formatter;定义了console、file、error三个handler,其中console使用brief formatter,file和error使用simple formatter;main.core logger使用file和error handler,root logger使用console handler:

version: 1
formatters:
  brief:
    format: "%(asctime)s - %(message)s"
  simple:
    format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
  console:
    class : logging.StreamHandler
    formatter: brief
    level   : INFO
    stream  : ext://sys.stdout
  file:
    class : logging.FileHandler
    formatter: simple
    level: DEBUG
    filename: debug.log
  error:
    class: logging.handlers.RotatingFileHandler
    level: ERROR
    formatter: simple
    filename: error.log
    maxBytes: 10485760
    backupCount: 20
    encoding: utf8
loggers:
  main.core:
    level: DEBUG
    handlers: [file, error]
root:
  level: DEBUG
  handlers: [console]

将上面的configuration setup可以通过以下方法:

import logging
import yaml
import logging.config
import os

def setup_logging(default_path='config.yaml', default_level=logging.INFO):
    if os.path.exists(default_path):
        with open(default_path, 'r', encoding='utf-8') as f:
            config = yaml.load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)

dictConfig通过将dict数据传递给dictConfigClass,然后返回对象调用configure函数使配置生效。

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

八、Django中使用logging

重新开个博客
Django 使用logging生成日志

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值