找回密码
 会员注册
查看: 23|回复: 0

Python的logging模块(日志、DEBUG、INFO、WARNING、ERROR、CRITICAL)

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
72018
发表于 2024-9-9 10:50:37 | 显示全部楼层 |阅读模式
1.前言logging是Python标准库中用于记录日志的模块。它提供了一种灵活且可配置的方式来在应用程序中记录各种信息,包括调试信息、警告和错误消息。无论是写框架代码还是业务代码,都离不开日志的记录,它能给我们定位问题带来极大的帮助。记录日志最简单的方法就是在我们想要记录的地方加上一句print。在简单的代码中或者小型项目中这么干一点问题都没有。就比如说我之前经常这样写日志:outputs=model(inputs)print(f"outputs.shape:{outputs.shape}")12这样记录日志当然是可以的,但当我们正式运行模型的时候,随着for循环的进行,一味的print会占用我们console的界面,所以我们会将这句print删除——我们好像做了一件事儿,但之后还要删了它,怪可惜的😂。所以我在训练模型的时候也会使用.txt文件,如下所示:withopen(f'{args.save_path}/logging.txt','a')asfile:file.write(f"Epoch[{epoch}/{args.epochs}]"f"TrainLoss:{train_loss:.4f}\t"f"TrainAcc:{train_accuracy:.4f}"f"ValLoss:{val_loss:.4f}\t"f"ValAcc:{val_accuracy:.4f}\t\t"f"BestAcc:{best_acc:.4f}({np.argmax(val_accuracy_lst)+1ifepoch!=1elseepoch})\n")1234567Epoch[1/50] TrainLoss:1.2728 TrainAcc:0.7058 ValLoss:1.1853 ValAcc:0.7708 BestAcc:0.7708(1)Epoch[2/50] TrainLoss:1.1682 TrainAcc:0.7653 ValLoss:1.1126 ValAcc:0.7826 BestAcc:0.7826(2)Epoch[3/50] TrainLoss:1.1011 TrainAcc:0.7853 ValLoss:1.0564 ValAcc:0.7968 BestAcc:0.7968(3)Epoch[4/50] TrainLoss:1.0322 TrainAcc:0.8071 ValLoss:0.9993 ValAcc:0.8181 BestAcc:0.8181(4)1234这样不占用console的空间,也非常干净利落,唯一的缺点就是我们需要保证.txt文件所在文件夹是存在的,如果不存在就会报错。后来我接到一个项目,需要对数据进行处理,这个过程中我需要不断查看tensor/array的shape,所以需要不断print(这个时候用.txt文件明显是不合适的),但是当我们处理好整个文件,就不需要print了。可是万一以后出现了bug,或者数据有变化,那么我还需要继续print,那我就陷入了纠结之中:保留print→项目在运行的时候console被占用,有点烦不保留print→不利于以后的debug所以我是这样做的:defparse_args():parser=argparse.ArgumentParser(description="示例")parser.add_argument("--seed",default=42,type=int,help="Randomseed")parser.add_argument("--device",default="cuda",help="trainingdevice")parser.add_argument("--batch_size",default=64,type=int,help='batch的大小')parser.add_argument("--epochs",default=200,type=int,metavar="N",help="总的epoch数量")parser.add_argument("--verbose",action='store_true',help="在数据处理中是否print一些东西")args=parser.parse_args()returnargsif__name__=="__main__":args=parse_args()print(f"input.shape:{input.shape}")ifargs.verboseelse...1234567891011121314这样我可以通过设置--verbose,从而确定是否要print。这样的确可以解决我的问题👍,但是这样也不太规范,因为如果我想将print的内容保存到本地的.txt文件中,那么代码就很麻烦了。所以说,print可以解决很多问题,但是在一些稍大一点的项目,有时候定位一个问题,需要查看历史日志定位问题,用print就不合时宜了。而且`print``打印出来的日志没有时间,不知道日志记录的位置,也没有可读的日志格式,还不能把日志输出到指定文件。。。。除非这些我们都全部自己重复造一遍轮子。最佳的做法是使用内置的logging模块,因为logging模块给开发者提供了非常丰富的功能。到这里我们可以总结一下,使用logging相比于print有以下几个优点:级别和过滤:logging允许我们设置不同的日志级别,如DEBUG、INFO、WARNING、ERROR和CRITICAL。这使得我们可以更精细地控制哪些信息被记录,以及在不同情况下打印不同级别的信息。输出多样性:logging支持多种输出方式,包括将日志信息写入文件、发送到网络服务、通过电子邮件通知等。这种多样性有助于在不同情况下有效地处理日志信息。格式化:logging允许我们自定义日志消息的格式,包括时间戳、日志级别和自定义消息。这使得日志信息更易于阅读和分析。异常处理:logging可以捕获和记录异常信息,帮助我们更容易地跟踪和调试问题。线程安全:logging是线程安全的,这意味着我们可以在多线程或多进程应用程序中安全地记录日志,而不必担心竞态条件。总之,使用logging比简单的print语句更适合在生产环境中记录和管理日志信息,特别是在大型应用程序中。通过设置不同的级别、输出方式和格式,我们可以更好地控制日志的生成和处理。2.logging的初级使用方法2.1基本使用那么logging怎么用呢,来看个例子:importlogginglogging.debug("这是一条[debug]日志!")logging.info("这是一条[info]日志!")logging.warning("这是一条[warning]日志!")logging.error("这是一条[error]日志!")logging.critical("这是一条[critical]日志!")12345678'运行运行运行之后的结果为:WARNING:root:这是一条[warning]日志!ERROR:root:这是一条[error]日志!CRITICAL:root:这是一条[critical]日志!123但是我们发现,我明明写了5条语句,为什么结果只有三条?这是因为logging的日志是有级别的,级别低的日志不会显示在console中,下面我们看一下具体的日志级别规则。2.2日志的级别级别级别数值使用时机DEBUG10详细信息,常用于调试INFO20程序正常运行过程中产生的一些信息WARNINGdefault30警告用户,虽然程序还在正常工作,但有可能发生错误ERROR40由于更严重的问题,程序已经不能执行一些功能了CRITICAL50严重错误,程序已经不能继续运行日志的默认级别是WARNING从DEBUG到CRITICAL,日志的级别越来越高,且logging默认显示的最低级别为WARNING,低于WARING的日志信息都不会输出。2.3修改日志级别logging提供日志级别可以很好的应对不同的使用场景,那么问题来了,如果我就是在debug,我想让debug级别的日志显示出来,该怎么办呢?我们可以修改日志的级别。即在开始记录日志前可以使用logging.basicConfig方法来设定日志级别:importlogging#修改默认的日志显示级别logging.basicConfig(level=logging.DEBUG)#DEBUG及以上的日志信息都会显示logging.debug("这是一条[debug]日志!")logging.info("这是一条[info]日志!")logging.warning("这是一条[warning]日志!")logging.error("这是一条[error]日志!")logging.critical("这是一条[critical]日志!")1234567891011'运行运行DEBUG:root:这是一条[debug]日志!INFO:root:这是一条[info]日志!WARNING:root:这是一条[warning]日志!ERROR:root:这是一条[error]日志!CRITICAL:root:这是一条[critical]日志!12345现在,我们所以级别的日志都会显示了。2.4将日志输出为文件前面的日志默认会把日志输出到标准输出流,就是只在命令行窗口(console/terminal)中输出,程序重启后历史日志没地方找(可以使用history查找,但那样太麻烦了),所以把日志内容永久记录是一个很常见的需求。同样通过配置函数logging.basicConfig可以指定日志输出到什么地方。importlogging#修改默认的日志显示级别logging.basicConfig(filename="test.log",#将日志保存到filename文件中level=logging.DEBUG)#DEBUG及以上的日志信息都会显示logging.debug("这是一条[debug]日志!")logging.info("这是一条[info]日志!")logging.warning("这是一条[warning]日志!")logging.error("这是一条[error]日志!")logging.critical("这是一条[critical]日志!")123456789101112'运行运行此时,console中不再显示日志内容了,但在本地会生成一个名为test.log的文件:Q:如果我们再运行一次,会怎样呢?A:我们发现追加内容了,仔细的同学会发现,logging.basicConfig有一个参数filemode=,就和我们将内容写到.txt文件一样,这里的filemode决定了我们是如何写的,详情如下:filemode值名称含义'w'写入(write)如果文件存在,将其截断为零长度;如果文件不存在,则创建一个新文件并打开以进行写入。'x'排他性(exclusive)如果文件存在,则打开操作失败(抛出错误)。如果文件不存在,则创建一个新文件并打开以进行写入。'a'追加(append)如果文件存在,将数据附加到文件末尾,而不是覆盖现有内容。如果文件不存在,则创建一个新文件并打开以进行写入。'b'二进制(binary)以二进制模式打开文件,可与上述任何模式组合使用(例如,'wb'、'xb'、'ab')。使用filemode参数,我们可以根据需要配置basicConfig()函数以控制日志文件的创建和写入方式。如果我们使用filemode='w',看一下效果:importlogging#修改默认的日志显示级别logging.basicConfig(filename="test.log",#将日志保存到filename文件中filemode='w',#写入的模式level=logging.DEBUG)#DEBUG及以上的日志信息都会显示logging.debug("这是一条[debug]日志!")logging.info("这是一条[info]日志!")logging.warning("这是一条[warning]日志!")logging.error("这是一条[error]日志!")logging.critical("这是一条[critical]日志!")12345678910111213'运行运行2.5日志的格式2.5.1示例默认输出的格式包含三部分:日志级别日志记录器的名字日志内容这三部分可以用|连接。如果我们想改变日志格式,例如想加入日期时间、显示日志器名字,我们是可以指定format参数来设置日志的格式:importlogging#修改默认的日志显示级别logging.basicConfig(filename="test.log",#将日志保存到filename文件中filemode='w',#写入的模式format="%(asctime)s|%(levelname)s|%(name)s|%(message)s",level=logging.DEBUG)#DEBUG及以上的日志信息都会显示logging.debug("这是一条[debug]日志!")logging.info("这是一条[info]日志!")logging.warning("这是一条[warning]日志!")logging.error("这是一条[error]日志!")logging.critical("这是一条[critical]日志!")1234567891011121314'运行运行2.5.2格式化参数日志格式化输出提供了非常多的参数,除了时间、日志级别、日志消息内容、日志记录器的名字个外,还可以指定线程名,进程名等等,下面是一个总结:关键字含义%(asctime)s记录的时间(包括日期和时间)。%(levelname)s日志级别(例如,DEBUG、INFO、WARNING、ERROR、CRITICAL)。%(message)s日志消息文本。%(name)s记录器的名称。%(module)s记录日志的模块的名称。%(filename)s记录日志的文件的名称(包括路径)。%(funcName)s记录日志的函数或方法的名称。%(lineno)d记录日志的代码行号。%(process)d进程ID。%(thread)d线程ID。在这个配置中,日志消息格式包括时间戳、记录器名称、日志级别和消息文本。我们可以根据实际需求灵活使用这些关键字来定制日志格式。Q:为什么有些关键字的结尾是s,有些是d?A:关键字的结尾是s或d取决于该关键字的数据类型和用途。具体来说:结尾为s的关键字表示字符串类型,通常用于表示文本信息,如%asctime和%levelname。例如,%(asctime)s代表一个时间戳的字符串。结尾为d的关键字表示整数类型,通常用于表示数字信息,如%lineno、%(process)d和%(thread)d。例如,%(lineno)d代表代码行号的整数值。这种命名约定有助于在日志消息格式中正确解释和处理不同类型的数据。例如,当我们使用%asctime时,日志系统知道它应该将时间戳转换为字符串并将其包含在日志消息中,而使用%lineno时,它知道应该将代码行号作为整数包含在消息中。这样可以确保日志消息格式正确地处理各种类型的数据。到这里为止,日志模块的基本用法就这些了,也能满足大部分应用场景,更高级的方法接着往下看,可以帮助我们更好的处理日志。3.logging的高级使用方法3.1记录器(Logger)3.1.1Logger的介绍前面介绍的日志记录,其实都是通过一个叫做日志记录器(Logger)的实例对象创建的,每个记录器都有一个名称,直接使用logging来记录日志时,系统会默认创建名为root的记录器,这个记录器是根记录器。记录器支持层级结构,子记录器通常不需要单独设置日志级别以及Handler(后面会介绍),如果子记录器没有单独设置,则它的行为会委托给父级。3.1.2Logger的继承关系记录器名称可以是任意名称,不过最佳实践是直接用模块的名称当作记录器的名字。命名如下:#实例化Logger对象logger=logging.getLogger(name=__name__)12其中__name__是当前运行的文件名。print(__name__)#__main__1'运行运行默认情况下,记录器采用层级结构,上句点作为分隔符排列在命名空间的层次结构中。层次结构列表中位于下方的记录器是列表中较高位置的记录器的子级。例如,有个名叫foo的记录器,而名字是foo.bar,foo.bar.baz,和foo.bam的记录器都是foo的子级。C:.│main.py││├─foo││bam.py││__init__.py│││└─bar│baz.py│__init__.py││└─zoo│bam.py│__init__.py│└─barbaz.py__init__.py1234567891011121314151617181920rootfoozoofoo.barfoo.bamfoo.bar.bazzoo.barzoo.bamzoo.bar.bazQ:foo,foo.bar,foo.bar.baz,foo.bam是什么?A:“foo”是计算机编程中常用的一个占位符或变量名,用于表示一个通用的、不特定的、或者临时的名称。它通常用于示例代码、教学材料或者临时测试中,以代表任何具体的名称或值。它没有固定的全称,因为它本身就是一个抽象的概念。在编程中,“foo”和类似的占位符如“bar”、“baz”经常被用来表示一些变量或数据,而不涉及具体的业务逻辑。这些名称通常用于帮助程序员理解代码的结构,而不是具体的内容。先在根目录下新建名为main.py的文件,内容如下:importfoofromfooimportbarfromfooimportbamfromfoo.barimportbazimportzoofromzooimportbarfromzooimportbamfromzoo.barimportbazif__name__=='__main__':pass123456789101112新建名为foo的文件夹,并在里面新建__init__.py、bam.py文件以及bar文件夹,内容分别为:#__init__.py:空的#bam.py:importlogginglogger=logging.getLogger(__name__)logger.info("thisisbam")#bar文件夹#新建__init__.py文件(空的)#新建baz.py文件,内容如下:logger=logging.getLogger(__name__)logger.info("thisisbaz")1234567891011新建名为zoo的文件夹,并在里面新建__init__.py、bam.py文件以及bar文件夹,内容和step.2一样。运行main.py文件。结果如下:INFO:foo:thisisfooINFO:foo.bar:thisisbarINFO:foo.bam:thisisbamINFO:foo.bar.baz:thisisbazINFO:zoo:thisiszooINFO:zoo.bar:thisisbarINFO:zoo.bam:thisisbamINFO:zoo.bar.baz:thisisbaz12345678这里我们设置foo和zoo两个记录器的级别均为INFO。如果我们设置foo的级别为INFO,而zoo记录器的级别为WARNING,那么结果如下:INFO:foo:thisisfooINFO:foo.bar:thisisbarINFO:foo.bam:thisisbamINFO:foo.bar.baz:thisisbaz1234需要注意的是:foo.bar这个记录器没有设置日志级别,就会向上找到已经设置了日日志级别的祖先,这里刚好找到父记录器foo的级别为INFO,如果foo也没设置,就会找到根记录器root,root默认的级别为WARNING。3.1.3Logger的操作Logger对象是Python中logging模块中的关键组件之一,它允许我们记录日志消息以便于调试和追踪程序的运行。以下是一些常见的Logger对象支持的操作:记录日志消息:通过调用Logger对象的方法,如debug(),info(),warning(),error(),exception(),和critical(),我们可以记录不同级别的日志消息。#实例化Logger对象logger=logging.getLogger()logger.debug("这是一个调试消息")logger.info("这是一个信息消息")logger.warning("这是一个警告消息")logger.error("这是一个错误消息")logger.critical("这是一个严重消息")12345678设置日志级别:我们可以使用setLevel()方法来设置记录器的日志级别,只有达到或超过此级别的日志消息才会被记录。logger.setLevel(logging.DEBUG)1添加处理程序:我们可以使用addHandler()方法将处理程序(例如文件处理程序、控制台处理程序)添加到记录器,以指定如何处理记录的日志消息。file_handler=logging.FileHandler("my_log.log")logger.addHandler(file_handler)12设置格式化:我们可以使用setFormatter()方法为处理程序设置自定义的日志消息格式。formatter=logging.Formatter('%(asctime)s-%(name)s-%(levelname)s-%(message)s')file_handler.setFormatter(formatter)12获取记录器名称:使用name属性,我们可以获取记录器的名称。logger_name=logger.name1记录异常:使用exception()方法,我们可以记录异常信息。try:#一些可能引发异常的代码exceptExceptionase:logger.exception("发生异常:%s",str(e))1234这些是一些常见的Logger操作,帮助我们在应用程序中有效地记录和管理日志。通过设置不同的处理程序、级别和格式化,我们可以定制日志记录以满足应用程序的需求。3.2处理器(Handler)3.2.1Handler的介绍记录器(Logger)负责日志的记录,但是日志最终记录在哪里,记录器(Logger)并不关心,而是交给了另一个家伙–处理器(Handler)去处理。3.2.2Handler的简单应用示例例如一个Flask项目,我们可能会将INFO级别的日志记录到文件,将ERROR级别的日志记录到标准输出,将某些关键日志(例如有订单或者严重错误)发送到某个邮件地址通知老板。这时候我们的记录器添加多个不同的处理器(Handler)来处理不同的消息日志,以此根据消息的重要性发送的特定的位置。项目LoggerStdoutStreamHandlerConsole/TerminalHTTPHandlerWebServerFileHandlerLocalFileSMTPHandlerEmailService3.2.3Handler的类别Python内置了很多实用的处理器,以下是常用的几种Handler(处理器):Handler名称作用⭐️StreamHandler将日志消息发送到标准输出流(通常是终端屏幕)。⭐️FileHandler将日志消息写入文件。⭐️BaseRotatingHandler一个基类,用于处理日志轮换的处理程序。⭐️TimedRotatingFileHandler基于时间的轮换文件处理程序,根据时间创建新的日志文件。SocketHandler将日志消息发送到网络套接字,用于远程日志记录。DatagramHandler将日志消息作为数据报发送到网络,通常用于UDP日志记录。SMTPHandler通过电子邮件将日志消息发送到指定的电子邮件地址。SysLogHandler将日志消息发送到Syslog服务器,通常用于UNIX和Linux系统。NTEventLogHandler将日志消息发送到Windows事件日志。HTTPHandler将日志消息通过HTTPPOST请求发送到指定的HTTP服务器。WatchedFileHandler类似于FileHandler,但会监视日志文件的变化并自动回滚日志文件。QueueHandler将日志消息排队,以便另一个进程或线程可以处理它们。NullHandler一个虚拟处理程序,将所有接收到的日志消息丢弃,用于关闭日志记录。这些处理程序可帮助我们控制如何处理和记录Python应用程序中的日志消息。3.2.4Handler的操作Handler是Python中logging模块中的另一个重要组件,用于定义如何处理记录的日志消息。和Logger一样,也有很多操作,以下是一些常见的Handler对象支持的操作:设置日志级别:我们可以使用setLevel()方法来设置处理程序的日志级别,只有达到或超过此级别的日志消息才会被处理。handler.setLevel(logging.DEBUG)1设置格式化:使用setFormatter()方法,我们可以为处理程序设置自定义的日志消息格式。formatter=logging.Formatter('%(asctime)s-%(name)s-%(levelname)s-%(message)s')handler.setFormatter(formatter)12添加到记录器:使用addHandler()方法,我们可以将处理程序添加到记录器,以指定如何处理记录的日志消息。logger.addHandler(handler)1移除处理程序:使用removeHandler()方法,我们可以从记录器中移除一个已添加的处理程序。logger.removeHandler(handler)1处理日志消息:处理程序的主要功能是处理日志消息,我们无需手动调用它,Logger会在调用记录器的日志方法时自动触发处理程序。关闭处理程序:使用close()方法,我们可以关闭处理程序,通常在应用程序退出时进行清理操作。handler.close()1刷新处理程序:一些处理程序,如文件处理程序,可能需要手动刷新以确保日志消息被写入文件。可以使用flush()方法来刷新处理程序。handler.flush()1这些是一些常见的Handler操作,它们允许我们定义如何处理和记录日志消息。通过设置不同的级别、格式化和将处理程序与记录器关联,我们可以定制日志处理以满足应用程序的需求。3.2.5不同Handler的代码示例importlogging#1.Logger##1.1实例化Logger对象logger=logging.getLogger(name=__name__)##1.2设置Logger对象的日志级别logger.setLevel(level=logging.DEBUG)#2.Handler##2.1创建Handler对象stream_handler=logging.StreamHandler()file_handler=logging.FileHandler(filename="advanced_usage_test.log",mode='a')##2.2设置Handler级别stream_handler.setLevel(logging.WARNING)file_handler.setLevel(logging.INFO)##2.3将Handler添加到Logger中logger.addHandler(hdlr=stream_handler)logger.addHandler(hdlr=file_handler)#3.Logger记录信息logger.debug("这是一条[debug]日志!")logger.info("这是一条[info]日志!")logger.warning("这是一条[warning]日志!")logger.error("这是一条[error]日志!")logger.critical("这是一条[critical]日志!")1234567891011121314151617181920212223242526272829'运行运行console的结果为:这是一条[warning]日志!这是一条[error]日志!这是一条[critical]日志!123advanced_usage_test.log的内容为:3.3日志等级规则在logging模块中,Logger和Handler的日志等级之间存在一种层次结构,具体的输出哪些等级的日志消息是由它们之间的关系决定的。以下是日志消息输出的规则:Logger的日志级别:Logger有一个单独的日志级别,通过logger.setLevel()方法来设置。此级别表示Logger对象本身能够处理的最低级别的日志消息。Handler的日志级别:Handler也有自己的日志级别,通过handler.setLevel()方法来设置。此级别表示Handler能够处理的最低级别的日志消息。最低级别的日志消息指的是该日志级别及以上的消息。在Python的logging模块中,日志级别之间存在一种层次结构,较低级别的消息会包括较高级别的消息。不同日志级别的层次结构(从低到高):DEBUGINFOWARNINGERRORCRITICAL现在,根据这两者的关系,决定日志消息的输出规则如下:如果Logger对象的日志级别(通过logger.setLevel()设置)比Handler的日志级别高,那么Logger会过滤掉低于其级别的日志消息,不传递给Handler处理。如果Logger对象的日志级别比Handler的日志级别低或相等,那么Logger会将所有符合其级别的日志消息传递给Handler处理。如果多个Handler关联到同一个Logger,并且它们的日志级别不同,那么Logger会根据每个Handler的级别来决定哪个Handler处理哪个日志消息。以下是一个示例来说明这个规则:importlogginglogger=logging.getLogger("example_logger")logger.setLevel(logging.DEBUG)file_handler=logging.FileHandler("example.log")file_handler.setLevel(logging.ERROR)console_handler=logging.StreamHandler()console_handler.setLevel(logging.INFO)logger.addHandler(file_handler)logger.addHandler(console_handler)logger.debug("这条日志消息不会被记录到文件和控制台")logger.info("这条日志消息会被记录到控制台,但不会被记录到文件")logger.error("这条日志消息会被记录到文件和控制台")123456789101112131415161718'运行运行console的结果:这条日志消息会被记录到控制台,但不会被记录到文件这条日志消息会被记录到文件和控制台12example.log的结果:根据上面的示例,logger.debug()的消息不会被记录到文件和控制台,因为它的级别比文件处理程序的级别高。logger.info()的消息会被记录到控制台,但不会被记录到文件,因为它的级别低于文件处理程序的级别。logger.error()的消息会被记录到文件和控制台,因为它的级别符合所有处理程序的级别。根据上面的例子,我们就知道我们之前写的代码是怎么回事了,具体分析如下所示。我们首先创建了一个名为logger的日志记录器对象,然后通过logger.setLevel()设置了日志级别为DEBUG,这意味着它会记录所有级别的日志消息。我们创建了两个处理程序,一个是stream_handler,另一个是file_handler,并分别设置了它们的日志级别。stream_handler的日志级别设置为WARNING,这意味着它将只处理WARNING、ERROR和CRITICAL级别的日志消息。file_handler的日志级别设置为INFO,这意味着它将处理所有级别的日志消息,包括INFO、WARNING、ERROR和CRITICAL(但不包括DEBUG)。接下来,我们使用不同级别的日志消息记录了一些示例消息,包括DEBUG、INFO、WARNING、ERROR和CRITICAL。根据上述配置,不同级别的日志消息将由不同的处理程序处理:DEBUG消息会被记录到file_handler中,因为根记录器的日志级别是DEBUG,而file_handler的级别也是INFO,满足了处理条件。INFO消息也会被记录到file_handler中,因为它的级别是INFO。WARNING、ERROR和CRITICAL消息会被stream_handler处理,因为stream_handler的级别是WARNING。这样,我们可以根据不同的处理程序和其级别设置来控制哪些级别的日志消息会被记录到不同的输出位置。这对于在应用程序中灵活地管理日志非常有用。简单来说,尽管我们将logger的级别设置为了DEBUG,但是debug记录的消息并没有输出,因为我们给两个Handler设置的级别都比DEBUG要高,所以这条消息被过滤掉了。3.4格式器(Formatter)格式器(Formatter)在文章的前面部分其实已经有所介绍,不过那是通过logging.basicConfig来指定的,其实格式器还可以以对象的形式来设置在Handler上。格式器可以指定日志的输出格式,要不要展示时间,时间格式什么,要不要展示日志的级别,要不要展示记录器的名字等等,都可以通过一个格式器对消息进行格式化输出。下面是logging库中Formatter的常见属性和它们的作用:fmt:格式字符串,用于指定日志消息的输出格式。格式字符串可以包含占位符,例如%(levelname)s(日志级别的名称)或%(message)s(日志消息的内容)。datefmt:日期时间格式字符串,用于指定时间戳的输出格式。如果日志消息包含时间戳,datefmt将定义如何呈现时间戳。style:格式化风格,可以是'%'(传统风格)或'{'(新式风格)。传统风格使用%占位符,而新式风格使用{}占位符。3.4.1Formatter的代码示例importlogging#1.Logger##1.1实例化Logger对象logger=logging.getLogger(name=__name__)##1.2设置Logger对象的日志级别logger.setLevel(level=logging.DEBUG)#2.Handler##2.1创建Handler对象stream_handler=logging.StreamHandler()file_handler=logging.FileHandler(filename="advanced_usage_test.log",mode='a')##2.2设置Handler级别stream_handler.setLevel(logging.WARNING)file_handler.setLevel(logging.INFO)##2.3将Handler添加到Logger中logger.addHandler(hdlr=stream_handler)logger.addHandler(hdlr=file_handler)#3.Formatter##3.1创建格式器formatter_full=logging.Formatter(fmt="%(asctime)s-%(name)s-%(levelname)s-[%(lineno)d]%(message)s",datefmt="%Y-%m-%d%H:%M:%S")#复杂的格式formatter_simple=logging.Formatter(fmt="%(asctime)s-%(levelname)s-%(message)s")#简单的格式##3.2应用Formatter(我们一般只给Handler添加Formatter,Logger就不设置了)stream_handler.setFormatter(formatter_simple)file_handler.setFormatter(formatter_full)#4.Logger记录信息logger.debug("这是一条[debug]日志!")logger.info("这是一条[info]日志!")logger.warning("这是一条[warning]日志!")logger.error("这是一条[error]日志!")logger.critical("这是一条[critical]日志!")123456789101112131415161718192021222324252627282930313233343536373839'运行运行console结果:2023-10-1620:02:19,238-WARNING-这是一条[warning]日志!2023-10-1620:02:19,238-ERROR-这是一条[error]日志!2023-10-1620:02:19,238-CRITICAL-这是一条[critical]日志!123advanced_usage_test.log的结果:注意:格式器(Formatter)只能作用在处理器(Handler)上,通过处理器(Handler)的setFromatter方法设置格式器(Formatter)一个Handler只能设置一个格式器(Formatter),是一对一的关系;logger与handler是一对多的关系,一个logger可以添加多个handlerhandler和logger都可以设置日志的等级,先是logger对日志等级进行过滤,之后再是Handler对日志等级进行过滤3.4.2Formatter格式汇总属性全拼(翻译)格式描述属性全拼(翻译)格式描述asctimeascenttime(时间升高)%(asctime)s获取时间的字符串表示msecsmilliseconds(毫秒)%(msecs)d获取毫秒数的字符串表示createcreationtime(创建时间)%(create)s获取日志记录的创建时间messagemessage(消息)%(message)s获取日志消息的字符串表示filenamefilename(文件名)%(filename)s记录日志的源文件名称nameloggername(记录器名称)%(name)s获取记录器名称的字符串表示funcNamefunctionname(函数名)%(funcName)s记录调用的函数名称pathnamefilepath(文件路径)%(pathname)s获取文件路径的字符串表示levelnamelevelname(级别名称)%(levelname)s记录的日志级别名称processprocessID(进程ID)%(process)d获取进程ID的字符串表示levelnolevelnumber(级别编号)%(levelno)s记录的日志级别编号processNameprocessname(进程名称)%(processName)s获取进程名称的字符串表示linenolinenumber(行号)%(lineno)d记录的源代码行号threadthreadID(线程ID)%(thread)d获取线程ID的字符串表示modulemodulename(模块名)%(module)s记录的源文件模块名称threadNamethreadname(线程名称)%(threadName)s获取线程名称的字符串表示3.5logging.basicConfig回到最开始的地方,logging.basicConfig()方法为我们干了什么,现在大概能猜出来了。来看Python源码中是怎么说的:Dobasicconfigurationfortheloggingsystem.Thisfunctiondoesnothingiftherootloggeralreadyhashandlersconfigured.Itisaconveniencemethodintendedforusebysimplescriptstodoone-shotconfigurationoftheloggingpackage.ThedefaultbehaviouristocreateaStreamHandlerwhichwritestosys.stderr,setaformatterusingtheBASIC_FORMATformatstring,andaddthehandlertotherootlogger.Anumberofoptionalkeywordargumentsmaybespecified,whichcanalterthedefaultbehaviour.进行日志系统的基本配置。如果根记录器(rootlogger)已经配置了处理程序,此函数将不起作用。这是一个方便的方法,旨在供简单的脚本使用,以一次性配置日志包。默认行为是创建一个写入到sys.stderr的StreamHandler,使用BASIC_FORMAT格式字符串设置格式化程序,然后将处理程序添加到根记录器。还可以指定一些可选的关键字参数,这些参数可以修改默认行为。1、创建一个root记录器2、设置root的日志级别为warning3、为root记录器添加StreamHandler处理器4、为处理器设置一个简单格式器logging.basicConfig()logging.warning("hello")12这两行代码其实就等价于:importloggingimportsys#1.定义Loggerlogger=logging.getLogger(name="root")logger.setLevel(logging.WARNING)#2.定义Handlerhandler=logging.StreamHandler(sys.stderr)#3.给logger添加handlerlogger.addHandler(handler)#4.定义Formatterformatter=logging.Formatter(fmt="%(levelname)s:%(name)s:%(message)s")#5.添加Formatterhandler.setFormatter(formatter)#6.记录日志logger.warning("hello")12345678910111213141516171819202122'运行运行WARNING:root:hello1其中,logging.basicConfig方法做的事情是相当于给日志系统做一个最基本的配置,方便开发者快速接入使用。它必须在开始记录日志前调用。不过如果root记录器已经指定有其它处理器,这时候我们再调用basciConfig,则该方式将失效,它什么都不做。sys.stderr是Python中的一个标准流对象,它代表标准错误输出流。在Python中,有三个标准流:sys.stdin:代表标准输入流,通常用于从用户获取输入。sys.stdout:代表标准输出流,通常用于向终端或控制台输出信息。sys.stderr:代表标准错误输出流,通常用于输出错误消息和警告。sys.stderr是通常用于将错误消息、异常信息和警告信息输出到控制台或日志文件中。在日志配置中,sys.stderr经常用作默认的输出目标,当没有指定其他处理程序时,日志消息将输出到标准错误输出流,即控制台。例如,通过以下方式可以将日志消息输出到sys.stderr:importlogging#创建一个日志记录器logger=logging.getLogger(__name)#创建一个处理程序,将日志消息输出到sys.stderrhandler=logging.StreamHandler(stream=sys.stderr)#将处理程序添加到记录器中logger.addHandler(handler)#记录一条日志消息logger.error("这是一条错误消息")12345678910111213在上述示例中,logging.StreamHandler(stream=sys.stderr)创建了一个处理程序,它将日志消息输出到sys.stderr,即标准错误输出流。3.6使用配置文件对logging进行配置日志的配置除了前面介绍的将配置直接写在代码中,还可以将配置信息单独放在配置文件中,实现配置与代码分离。3.6.1配置文件配置文件logging.cfg内容如下:[loggers]keys=root[handlers]keys=consoleHandler[formatters]keys=simpleFormatter[logger_root]level=DEBUGhandlers=consoleHandler[handler_consoleHandler]class=StreamHandlerlevel=DEBUGformatter=simpleFormatterargs=(sys.stdout,)[formatter_simpleFormatter]format=%(asctime)s-%(name)s-%(levelname)s-%(message)sdatefmt=%Y-%m-%d%H:%M:%S12345678910111213141516171819202122这是一个典型的Python标准库logging模块的配置文件,用于配置日志系统的各个组件,包括日志记录器(loggers)、处理程序(handlers)和格式化器(formatters)。让我们一步一步分析文件的内容:[loggers]部分定义了日志记录器的名称,这里只有一个名为“root”的日志记录器。[handlers]部分定义了处理程序的名称,这里只有一个名为“consoleHandler”的处理程序。[formatters]部分定义了格式化器的名称,这里只有一个名为“simpleFormatter”的格式化器。[logger_root]部分配置了根日志记录器,这是整个日志系统的根。该记录器的配置包括:level:将日志记录器的级别设置为DEBUG,表示它将处理所有级别的日志消息。handlers:将处理程序“consoleHandler”分配给该记录器。[handler_consoleHandler]部分配置了“consoleHandler”处理程序,这是用于将日志消息输出到控制台的处理程序。配置包括:class:指定处理程序的类为StreamHandler,表示将日志消息写入流。level:将处理程序的级别设置为DEBUG,表示它将处理所有级别的日志消息。formatter:将格式化器“simpleFormatter”分配给该处理程序。args:这里使用了参数(sys.stdout,),表示将日志消息写入标准输出流(sys.stdout)。[formatter_simpleFormatter]部分配置了“simpleFormatter”格式化器,用于定义日志消息的格式。配置包括:format:定义了日志消息的格式,包括时间戳、记录器名称、日志级别和消息内容。datefmt:定义了时间戳的格式,包括年月日和时分秒。这个配置文件定义了一个简单的日志系统,根日志记录器将日志消息输出到控制台,并使用了名为“simpleFormatter”的格式化器,以指定日志消息的输出格式。日志消息的级别从DEBUG到更高级别的都会被记录。我们可以在代码中使用这个配置文件来配置logging模块,以实现相同的日志记录行为。3.6.2加载配置文件importloggingimportlogging.config#1.加载配置文件logging.config.fileConfig("logging.cfg")#2.创建loggerlogger=logging.getLogger()#3.记录日志logger.debug("这是一条[debug]日志!")logger.info("这是一条[info]日志!")logger.warning("这是一条[warning]日志!")logger.error("这是一条[error]日志!")logger.critical("这是一条[critical]日志!")12345678910111213141516console的结果:2023-10-1620:23:22-root-DEBUG-这是一条[debug]日志!2023-10-1620:23:22-root-INFO-这是一条[info]日志!2023-10-1620:23:22-root-WARNING-这是一条[warning]日志!2023-10-1620:23:22-root-ERROR-这是一条[error]日志!2023-10-1620:23:22-root-CRITICAL-这是一条[critical]日志!12345知识来源pythonlogging日志模块详解atefmt`:定义了时间戳的格式,包括年月日和时分秒。这个配置文件定义了一个简单的日志系统,根日志记录器将日志消息输出到控制台,并使用了名为“simpleFormatter”的格式化器,以指定日志消息的输出格式。日志消息的级别从DEBUG到更高级别的都会被记录。我们可以在代码中使用这个配置文件来配置logging模块,以实现相同的日志记录行为。3.6.2加载配置文件importloggingimportlogging.config#1.加载配置文件logging.config.fileConfig("logging.cfg")#2.创建loggerlogger=logging.getLogger()#3.记录日志logger.debug("这是一条[debug]日志!")logger.info("这是一条[info]日志!")logger.warning("这是一条[warning]日志!")logger.error("这是一条[error]日志!")logger.critical("这是一条[critical]日志!")12345678910111213141516console的结果:2023-10-1620:23:22-root-DEBUG-这是一条[debug]日志!2023-10-1620:23:22-root-INFO-这是一条[info]日志!2023-10-1620:23:22-root-WARNING-这是一条[warning]日志!2023-10-1620:23:22-root-ERROR-这是一条[error]日志!2023-10-1620:23:22-root-CRITICAL-这是一条[critical]日志!12345知识来源pythonlogging日志模块详解Python基础之标准库logging你还在用print来调试程序吗,OUT啦
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-10 22:49 , Processed in 0.438915 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表