python学习
环境搭建 虚拟环境的安装
变量命名规范和特性
数据类型 序列类型字符串 取值和切片
字符串的常用方法
交互输入
序列类型 列表
序列类型 元组 tuple
数据类型 映射类型字典
数据类型 集合
if条件语句
循环语句
VSCode 运行 python 输出端乱码解决
Python 日期和时间
python 函数
Python 模块与包
Python 文件I/O
fileinput 模块
类
Python项目生成依赖包清单requirements .txt文件
logging模块
正则表达式
异常处理
导入隔壁文件夹下的模块
logging
json
并发
flask
centos7安装python3.11
本文档使用 MrDoc 发布
-
+
首页
logging模块
## logging.basicConfig()函数说明 ``` import logging logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG) 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ö') ``` 该方法用于为logging日志系统做一些基本配置,方法定义如下: ``` logging.basicConfig(**kwargs) ``` 该函数可接收的关键字参数如下: 参数名称 | 描述 * | - filename | 指定日志输出目标文件的文件名,指定该设置项后日志信心就不会被输出到控制台了 filemode | 指定日志文件的打开模式,默认为'a'。需要注意的是,该选项要在filename指定时才有效 format | 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。 datefmt | 指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效 level | 指定日志器的日志级别 stream | 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常 style | Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为'%'、'{'和'$',默认为'%' handlers | Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。 ## logging模块定义的格式字符串字段 我们来列举一下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)s | pathname的文件名部分,包含文件后缀 module | %(module)s | filename的名称部分,不包含后缀 lineno | %(lineno)d | 调用日志记录函数的源代码所在的行号 funcName | %(funcName)s | 调用日志记录函数的函数名 process | %(process)d | 进程ID processName | %(processName)s | 进程名称,Python 3.1新增 thread | %(thread)d | 线程ID threadName | %(thread)s | 线程名称 ## logging模块日志流处理流程 ### logging日志模块四大组件 在介绍logging模块的日志流处理流程之前,我们先来介绍下logging模块的四大组件: 组件名称 | 对应类名 | 功能描述 * | - | - 日志器 | Logger | 提供了应用程序可一直使用的接口 处理器 | Handler | 将logger创建的日志记录发送到合适的目的输出 过滤器 | Filter | 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 格式器 | Formatter | 决定日志记录的最终输出格式 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.setLevel() | 设置日志器将会处理的日志消息的最低严重级别 Logger.addHandler() 和 Logger.removeHandler() | 为该logger对象添加 和 移除一个handler对象 Logger.addFilter() 和 Logger.removeFilter() | 为该logger对象添加 和 移除一个filter对象 | 关于Logger.setLevel()方法的说明: 内建等级中,级别最低的是DEBUG,级别最高的是CRITICAL。例如setLevel(logging.INFO),此时函数参数为INFO,那么该logger将只会处理INFO、WARNING、ERROR和CRITICAL级别的日志,而DEBUG级别的消息将会被忽略/丢弃。 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的日志时还是需要该方法来完成。 **那么,怎样得到一个Logger对象呢?**一种方式是通过Logger类的实例化方法创建一个Logger类的实例,但是我们通常都是用第二种方式--logging.getLogger()方法。 logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为'root'。若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。 **关于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对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求: 1)把所有日志都发送到一个日志文件中; 2)把所有严重级别大于等于error的日志发送到stdout(标准输出); 3)把所有严重级别为critical的日志发送到一个email邮件地址。 这种场景就需要3个不同的handlers,每个handler复杂发送一个特定严重级别的日志到一个特定的位置。 一个handler中只有非常少数的方法是需要应用开发人员去关心的。对于使用内建handler对象的应用开发人员来说,似乎唯一相关的handler方法就是下面这几个配置方法: 方法 | 描述 * | - Handler.setLevel() | 设置handler将会处理的日志消息的最低严重级别 Handler.setFormatter() | 为handler设置一个格式器对象 Handler.addFilter() 和 Handler.removeFilter() | 为handler添加 和 删除一个过滤器对象 需要说明的是,应用程序代码不应该直接实例化和使用Handler实例。因为Handler是一个基类,它只定义了素有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'信息的出现。 --- #### 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新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%' --- #### 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表示可以通过过滤。 **说明:** 如果有需要,也可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。 我们还可以通过filter做一些统计工作,比如可以计算下被一个特殊的logger或handler所处理的record数量等。 ### logging日志流处理流程 下面这个图描述了日志流的处理流程: ![](/media/202302/2023-02-06_152005_1869610.21075051242600917.png) 我们来描述下上面这个图的日志流处理流程: 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四大组件记录日志 现在,我们对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') 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') ``` ## 配置logging的几种方式 作为开发者,我们可以通过以下3中方式来配置logging: 1)使用Python代码显式的创建loggers, handlers和formatters并分别调用它们的配置函数; 2)创建一个日志配置文件,然后使用fileConfig()函数来读取该文件的内容; 3)创建一个包含配置信息的dict,然后把它传递个dictConfig()函数; ### 使用Python代码实现日志配置 ``` # 创建一个日志器logger并设置其日志级别为DEBUG logger = logging.getLogger('simple_logger') logger.setLevel(logging.DEBUG) # 创建一个流处理器handler并设置其日志级别为DEBUG handler = logging.StreamHandler(sys.stdout) handler.setLevel(logging.DEBUG) # 创建一个格式器formatter并将其添加到处理器handler formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) # 为日志器logger添加上面创建的处理器handler logger.addHandler(handler) # 日志输出 logger.debug('debug message') logger.info('info message') logger.warn('warn message') logger.error('error message') logger.critical('critical message') ``` ### 使用配置文件和fileConfig()函数实现日志配置 ``` # 读取日志配置文件内容 logging.config.fileConfig('logging.conf') # 创建一个日志器logger logger = logging.getLogger('simpleExample') # 日志输出 logger.debug('debug message') logger.info('info message') logger.warn('warn message') logger.error('error message') logger.critical('critical message') ``` 配置文件logging.conf内容如下: ``` [loggers] keys=root,simpleExample [handlers] keys=fileHandler,consoleHandler [formatters] keys=simpleFormatter [logger_root] level=DEBUG handlers=fileHandler [logger_simpleExample] level=DEBUG handlers=consoleHandler qualname=simpleExample propagate=0 [handler_consoleHandler] class=StreamHandler args=(sys.stdout,) level=DEBUG formatter=simpleFormatter [handler_fileHandler] class=FileHandler args=('logging.log', 'a') level=ERROR formatter=simpleFormatter [formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt= ``` --- 1.关于fileConfig()函数的说明: 该函数实际上是对configparser模块的封装 **函数定义:** 该函数定义在loging.config模块下: ``` logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True) ``` 参数: * fname:表示配置文件的文件名或文件对象 * defaults:指定传给ConfigParser的默认值 * disable_existing_loggers:这是一个布尔型值,默认值为True(为了向后兼容)表示禁用已经存在的logger,除非它们或者它们的祖先明确的出现在日志配置中;如果值为False则对已存在的loggers保持启动状态。 --- https://www.cnblogs.com/yyds/p/6885182.html 参考文档 ## 示例代码 ``` #!/usr/bin/env python # coding:utf-8 import logging,time import pathlib from src.utils.config import Config def get_logger(log_path=None): # log_path是存放日志的路径,如果不存在这个logs文件夹,那么需要创建出来。 if not log_path: conf = Config() log_path = conf.log_path else: log_path = pathlib.Path(log_path) if not log_path.exists(): log_path.mkdir() logname = str(log_path.joinpath('%s.log'%time.strftime('%Y_%m_%d'))) logger = logging.getLogger() logger.setLevel(logging.DEBUG) # 日志输出格式 formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') # 创建一个FileHandler,存储日志文件 fh = logging.FileHandler(logname, encoding='utf-8') fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) # 创建一个StreamHandler,用于输出到控制台 sh = logging.StreamHandler() sh.setLevel(logging.DEBUG) sh.setFormatter(formatter) # 如果不判断是否为空,那么在多次调用函数get_logger函数后,会添加多个handler到logger.handlers中,导致出现日志重复输出问题。 # 参考链接:https://www.cnblogs.com/huang-yc/p/9209096.html if not logger.handlers: logger.addHandler(sh) logger.addHandler(fh) return logger if __name__ == '__main__': log_path = "./log" log = get_logger(log_path) log.info(u'基础信息') log.debug(u'调试信息') log.warning(u'警告信息') log.error(u'错误信息') ```
admin
2023年2月6日 16:08
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码