当前位置:   article > 正文

如何在Python项目中插入日志_python 写入日志

python 写入日志

如何在Python项目中插入日志

可以说,python自带的标准日志库logging,是每个写python的程序员从新手小白到进阶小白(哈哈)必须会的。本文将介绍三个典型的场景,让你理解,什么时候应该使用日志,以及用哪种方式使用日志。

新手时期的典型场景

新手时期的典型代码基本都是如下形式的:

import xxx

def func1(xx):
    xxxx
    print(...)
    xxxx

def func2(xx):
    xxxx
    print(...)
    xxxxx
...
if __name__ == '__main__':
    func1(...) ... func2(...)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这样的程序的问题在我这里是以下几个:

  1. 修改很繁琐,比如想对不同的print进行区分,你就得往里面写一大堆区分的符号,常见的比如 -----val_1-----;一个小部分debug完了你又得上去删除,有时候你写多了你得到处找,到处去注释

  2. 如果涉及递归、循环等会导致大量打印的内容,并且你在终端,比如VScode的终端运行程序,可能都翻不到最顶上,丢失很多展示内容,并且可能只能看一次,你再运行别的程序或者关闭了这个想明天再看,就没了

1. logging 替代 print

为了解决问题1,一般是会使用logging去替代print。有什么好处呢?logging可以用INFO, WARNING, DEBUG, ERROR那些等级去控制,比如你找bug的时候你设置logging等级为比较低的Debug,等到你把你的问题解决完了,你不用到处去找你的logging语句把它们注释掉,你直接把原来的logging等级设置为更高一级的INFO,那些输出就统统失效了

print('...') -> logging.debug('...')
# 当logging的日志级别是debug的时候才会输出,高于debug等级时,不会输出
  • 1
  • 2

最基础的使用方式是直接使用logging.xxxx

这样的使用方式下,在主程序中通过 logging.basicConfig() 快速设置输出日志文件的路径、格式、等级等,再在任意位置用 logging.debug()/info()/… 代替 print,那么就会统一使用该配置进行日志输出

import logging

logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)

# logging.debug(xxx)中的内容为message,而我们设置的format就是message如何和其他的信息结合,比如我们想有记录日志的时间戳等

logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')

# 这样会记录得到
23-04-09 12:10:32 root:DEBUG:This is a debug message
23-04-09 12:10:32 root:INFO:This is an info message
23-04-09 12:10:32 root:WARNING:This is a warning message
23-04-09 12:10:32 root:ERROR:This is an error message
23-04-09 12:10:32 root:CRITICAL:This is a critical message

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

但是用着用着就会发现,还存在一些问题:

  1. 前面说的,如果有递归和循环,那不同的函数里写的日志我想要区分开怎么办?-> 取不同的日志器的名字

  2. 不同的日志都写在一个文件里,就算前面写着不同的名字,比如上面的例子里日志器都叫root,还是不好看怎么办?比如说,我想要不同函数把不同的日志写在不同的文件里面,我运行一次程序,可以有一个简要版本的日志文件生成出来,还同时有一个细节版本生成出来,让我可以关注一些特定环节的细节

一个一个来

2. 不同的logger

我们需要对logging了解得更深入一些。

上面直接使用logging进行的快速使用,本质上是依靠四大底层组件:

  • Logger: 应用程序的小秘,应用程序要记录啥就跟他说,哎,logger,你帮我写一串啥写到哪里
    • 但是logger他也能力有限,他会让更专业的人去干各种专门的事。
  • filter:专门对要记录的信息进行细粒度的筛选和过滤,决定哪些要记录,哪些不记录
  • formatter:专门把确定要记录的东西结合你想要的信息,变成你想要的格式
  • handler:专门负责把日志写到文件系统里面。

当然了,程序员是他们的牛马,大牛程序员负责编写好filter, formatter, handler这些专业人士,封装在logging库里面,并且提供一些改装他们的接口,调包侠程序员们就负责把这些专业人士调教改装,等到专业人士都准备好了,你再从应用程序的视角,仅仅和logger进行简单的沟通就好了

通常,我们可以准备一份配置文件logging.conf,不同的logger,有不同的format,等级等

[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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

然后在应用程序中,就可以在不同的函数里按需调取不同的logger。但是注意,logger是遵循单例模式的,也就是说,实际上在一个python解释器中只有一个最最底层的rootLogger,我们创造的那些叫不同的名字、用不同的格式,在我们看来好像他们是不同的,但是实际上他们都会最终利用rootLogger的能力,所以我们不能创造logger实例,比如logger_A = logger()这样,我们只能使用logging.getLogger(name)来取得一个已经存在的logger,但是我们给他不同的功能。知道就可以

使用时:

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')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

基本没有很大的改变,但是确实会让日志中输出的内容更加丰富,有的前面写的root: message balabala;有的前面写的 simpleExample: message balabala,并且可以具备详略得当的格式

3. 不同logger输出到不同的日志文件中

因为是我遇到的一个场景,所以单独讲讲如何把不同日志输出到不同的日志文件中,本质上还是运用2中的知识。我的场景是这样的:我的主程序中有两次多进程相关的内容,一方面我需要记录主程序运行过程中,两次结果汇总是否正确,一方面我需要知道子进程的详细处理过程是否正确。因此就有这样的需求,把主程序的输出到一个文件,把子进程的输出到另外的几个文件里,这样看起来也方便,不会多个进程的输出混在一起。(如果有更好的做法还希望大佬们教教我呀,交流一下)

核心思想就是为一个logger准备它独有的handler,因为handler是负责发送日志message到具体文件的嘛,当然了,它也可以有独有的formatter那些,因为涉及格式

# 某个子进程函数
def worker(...):
    specified_logger = logging.getLogger(log_name) # 取个logger名字
    
    # 创建专有的handler, formatter等
    handler = logging.FileHandler(file_name)
    handler.setFormatter(specified_formatter) 
    
    # 传达给logger
    specified_logger.setLevel(level)
    specified_logger.addHandler(handler)
    
    # 接下来就可以使用该logger输出到不同文件了
    specified_logger.info('...')
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

有时候,我们希望每个函数独立默默地将自己的log输出到对应的文件就行了,不希望它们抢着一起输出到命令行中,此时对每个logger设置logger_name.propagate=False就可以了,它们会抢着输出到命令行,是因为这些自定义的logger其实都是会把message回传到root_logger的,root_logger的handler中有命令行输出的streamHandler,所以就会输出到命令行。如果我们关掉logger的propagate,message就不会被root_logger获取

Conclusion

本篇是python中的一个小但是重要的知识点,也确实困扰过我一阵子,但是后来就会发现很多思想都是统一的。比如写python的人肯定也绕不开matplotlib这个库,深入了解就会发现,它也是一个Artist对象做你的小秘,然后指导专业的Renderer负责专门的显色细节,在FigureCanvas(画布对象)上面画画。明白这一点以后,能够做出很多你需求定制化的更高级的事情,而不是被高级API束手束脚的。

如有错误,还请指正!拜托拜托!

(其实一直蛮害怕半桶水晃荡的时候分享自己的学习心得的,因为能想起学生时代,你和别人对答案,别人在你的答案的基础上,回去琢磨完发现你的答案错了,但是他不告诉你,第二天他对你错的场景,那真是有心理阴影哈哈哈)

But anyway, 做费曼学习法的践行者!必须有人先开始!

Reference

非常推荐阅读的官方文档,说得也很清楚,就是觉得进阶的示例还是少了一些

https://docs.python.org/3/howto/logging.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/392380
推荐阅读
相关标签
  

闽ICP备14008679号