资讯专栏INFORMATION COLUMN

python logging 日志重复打印问题定位

crossea / 3334人阅读

摘要:在同一系统中我们有时要用到这种机制来方便日志打印,因此有时会不同进程使用同一前缀名来初始化。避免日志重复的原则是在名有相同前缀的情况下对于一个模块两个进程调的情况,涉及到会被其他进程的模块,不应触发任何同名前缀的的操作。

图:

图编号按顺序1-4,嫌长可以跳过定位过程看总结

定位过程:

我司项目几个服务进程的初始化log都是这样的:

这些进程都会初始化一个叫 sdsomlogger,并且把handler加到了这个logger对象中,后面getlogger的时候我们是 sdsom.xx, 这个按点分隔会导致认为是子logger,比如是sdsom.A,就会新建一个loggersdsom.A,然后把 sdsom 这个logger设为它的parent(图1),打日志的时候,会一直往上遍历,把所有parent的所有handler都打一遍(图2)。这些实例进程间是独立的,但如果在一个进程里,比如在A进程中 import 了 B 的模块,而这个模块 import 了B自己的log.py模块,触发一次 addHandler (图3),就把 B 的 handler 加进了 A进程的 sdsom logger里(把它设为了parent),所以 A 的 sdsom logger里有两个handler (图4),于是A 的log同时打到了B的日志文件里。(注意对比图3 图4的对象地址 是一致的)

这个logger父子关系前人要这么用的原因,我估计是我们项目的common这个模块,用父子关系可以实现这样一个方式:不需另外初始化,log = logging.getLogger("sdsom.common") 只需要执行这一句,这个loggerparent就被设为 <import这个common模块的> 进程的 sdsom logger,实际上sdsom.xxx 点号后面的内容都没有影响了,这个common logger打印时,会调parent,于是也就被相应进程的handler打印了。
本身也算方便的机制,但由于这种方式内部实现不可见, 容易误用。

如果要共享日志, 还有一种方式就是对相应的logger显式加handler
比如要在其他日志里打印zerorpc的日志, 我们大部分日志初始化处都有这句: logging.getLogger("zerorpc").addHandler(handler), 给rpcLogger加上自己的handler就可以了,由于有了handler,那么只要zerorpc的源码里是getLogger("zerorpc")的(实际源码中一般是getLogger(__name__),在包内__name__即为"zerorpc.xxx"),日志就能打印到对应进程的日志里。
所以我们完全可以不用父子关系,而是像zerorpc一样在进程logger初始化的地方加上:
logging.getLogger("common").addHandler(handler)
然后common里的模块直接log = logging.getLogger("common")用即可,为避免和三方库重复要注意一下命名
当然还有一种方式就是自己的handler也通过函数触发,不要在模块全局上执行,加入一个函数手动调,只在进程初始化时调。

总结:

logging的父子关系是一个基础机制,稍微看下源码即可理解(其实主要就是图1图2):以点号.分隔,取最后一个点号的左边为前缀,以此前缀名作父,一个logger触发记录时,会调用所有父亲的handler。在同一系统中我们有时要用到这种机制来方便日志打印,因此有时会不同进程使用同一前缀名来初始化logger。这时,不同进程的模块若有相互import,容易造成一个日志打到多个日志文件里。如:

进程A:
A.py:
logger = logging.getLogger("xxsystem.A")
logger.addHandler(logging.FileHandler("service1.log"))
进程B 两个模块:

B.py:
from C import func
logger = logging.getLogger("xxsystem.B")
logger.addHandler(logging.FileHandler("service2.log"))

C.py:
from A import func

这样,就会造成B进程的log总是同时打到两个service1.log, service2.log日志文件里。这里是简化环境,只要B的import树里有A模块,就会造成同样结果。

避免日志重复的原则是:
logger名有相同前缀的情况下,对于一个模块两个进程调的情况,涉及到会被其他进程import的模块,不应触发任何同名前缀的loggeraddHandler操作。 (不能import <调用了addHandler方法的> 模块,自身也不能执行getLogger(prefix).addHandler

实际上我司使用这种机制本来也没有什么问题,只要注意不要随便import,都用getLogger即可。但由于代码不规范还是出现了不应有的import 日志初始化模块的情况。

要达到:

哪个进程调用模块,日志就打在那个进程对应的日志里:
a)getLogger,只要前缀相同,就会把当前进程的"prefix" logger设为父, 由于上面说的原因,这个logger会且只会被打到调用它的进程中(自己的handler没有初始化过)
b)logger对象

无论哪个进程调用模块,日志都打在自己规划所属的进程对应日志里:
不要有任何父子关系, 日志名不要带点。这时反过来,必须调用日志初始化模块触发初始化,而不能只用getLogger, 否则是一个空logger,哪里都不会打印。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44962.html

相关文章

  • 基于Selenium + Python的web自动化框架

    摘要:一什么是是一个基于浏览器的自动化工具,她提供了一种跨平台跨浏览器的端到端的自动化解决方案。模块主要用来记录用例执行情况,以便于高效的调查用例失败信息以及追踪用例执行情况。测试用例仓库用例仓库主要用来组织自动化测试用例。 一、什么是Selenium? Selenium是一个基于浏览器的自动化工具,她提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分:...

    sunny5541 评论0 收藏0
  • Python中的logging模块

    摘要:最近修改了项目里的相关功能,用到了标准库里的模块,在此做一些记录。可能没有线程名。可能没有用户输出的消息日志级别有如下级别,,,,默认级别是,模块只会输出指定以上的。在或者中这是很常见的方式。正常的做法应该是全局只配置一次。 最近修改了项目里的logging相关功能,用到了python标准库里的logging模块,在此做一些记录。主要是从官方文档和stackoverflow上查询到的一...

    zsirfs 评论0 收藏0
  • pythonlogging 模块浅析

    摘要:的继承关系使用做日志输出时,首先我们需要一个创建一个对象。再设计多级别的日志系统时,尤其要注意这点。当然,这样做其实是有悖于的本意的。是什么是一个程序内全局唯一的,所有对象的祖先。因此,直接修改是危险的。 0x00 python logging的继承关系 使用python做日志输出时,首先我们需要一个创建一个Logger对象:import logging; logger = log...

    cooxer 评论0 收藏0
  • Unity命令行模式,也能「日志实时输出」

    摘要:类似这样执行打印最终输出的日志要想在命令行模式工作的时候,查看它的编译进度,霖哥一般会远程跑进执行编译工作的机器,然后用命令,把它的日志实时输出来嗯,这相当的不科学啊。我是霖哥,一个商学院毕业的程序员,一个游戏开发工程师。 showImg(https://segmentfault.com/img/remote/1460000008856262); 如果你使用过Unity命令行模式(ba...

    whjin 评论0 收藏0
  • python学习笔记-装饰器

    摘要:装饰器介绍中的装饰器的目的是为一个目标函数添加额外的功能却不修改函数本身。装饰器的本身其实是一个特殊的函数。那么有啥更好的解决方式呢装饰器代码像上面这么写,可以较好地解决了上面提到的第一个问题。装饰器语法糖放在函数前面,相当于执行了等。 怎么理解python中的装饰器 一个比喻 知乎上有一个比较形象的比喻 https://www.zhihu.com/questio...:人类穿着内裤很...

    张金宝 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<