python的logging日志记录模块非常强大,使用也很简单,但是特别容易出各种意外状况,打印各种出乎意料的log。最近对logging的一些原理进行了学习,再此做个记录,以备忘。

首先全面的了解一下整体的结构。logging默认就有一个root的Logger对象,输入logging.root可以看到,默认为warning级别:

>>> logging.root
<RootLogger root (WARNING)>

用户自行创建的所有logger对象,都是root的子对象。

>>> logger = logging.getLogger('logger')
>>> logger.parent
<RootLogger root (WARNING)>

需要注意的是,当getLogger()不带参数时,返回的就是rootLogger对象本身。

>>> logger = logging.getLogger()
>>> logger
<RootLogger root (WARNING)>
>>> logger is logging.root
True

创建了Logger对象,接下来我们需要创建handler对象,handler对象是用来配置处理log时用的格式,级别等等特性的,我们可以根据需求创建各种不同的handler,比如将log记录保存在文件的FileHandler,将log输出到屏幕的StreamHandler,支持日志根据时间回滚的TimedRotatingFileHandler,根据文件大小回滚的RotatingFileHandler等等(回滚不太好理解,根据时间或文件大小回滚其实就是根据时间或者文件大小来保存日志,比如只保存3天的日志,超过3天的就自动删除,或者设置日志文件只能为一定大小,超过就删除)。

各种handler网上各种文章都有介绍,或者查官方文档,这里就不列举了。稍微一提的是,最常用的FileHandler和StreamHandler在logging下,其它的handler在logging.handlers下。另外,logger和handler的level可以分别设置,以满足不同的需求。

创建了handler,就要把它添加到logger对象中去,logger对象有一个handlers属性,其实就是一个简单的列表,可以看到所有添加给它的hanlder。如下:

>>> logger = logging.getLogger('spam')
>>> handler = logging.StreamHandler()
>>> logger.addHandler(handler)
>>> logger.handlers
[<StreamHandler <stderr> (NOTSET)>]

可见,logger对象添加了handler,是一个输出到stderr,级别未设置的handler。此时,就可以通过logger对象输出各种logger了,如:

>>> logger = logging.getLogger('spam')
>>> handler = logging.StreamHandler()
>>> formatter = logging.Formatter("%(asctime)s-%(levelname)s-%(message)s")
>>> handler.setFormatter(formatter)
>>> handler.setLevel(logging.INFO)
>>> logger.addHandler(handler)
>>> logger.handlers
[<StreamHandler <stderr> (INFO)>]
>>> logger.setLevel(logging.INFO)
>>> logger.info('info')
2019-03-31 11:59:41,933-INFO-info

对整体有个大体的了解,接下来就可以谈谈常见的大坑了。

首先,平时在log输出的时候,经常会发现,莫名奇妙的会输出重复的内容,主要有以下几个可能的原因:

1、前面说过,任何自定义的logger对象都是rootLogger的子对象,就像这样,rootLogger(parent)->Logger(child),而当你使用自定义的logger记录日志的时候,它会从子到父,从左到右依次执行所有的handler来输出,看代码:

>>> rootLogger = logging.getLogger()
>>> rootLogger.setLevel(logging.INFO)
>>> roothandler = logging.StreamHandler()
>>> fmt = logging.Formatter("%(name)s-%(levelname)s-%(message)s")
>>> roothandler.setLevel(logging.INFO)
>>> roothandler.setFormatter(fmt)
>>> rootLogger.addHandler(roothandler)
>>> rootLogger.handlers
[<StreamHandler <stderr> (INFO)>]
>>> childLogger = logging.getLogger("child")
>>> childLogger.setLevel(logging.INFO)
>>> childhandler = logging.StreamHandler()
>>> childhandler.setLevel(logging.INFO)
>>> childhandler.setFormatter(fmt)
>>> childLogger.addHandler(childhandler)
>>> childLogger.handlers
[<StreamHandler <stderr> (INFO)>]
>>> childLogger.info('i am child')
child-INFO-i am child
child-INFO-i am child

日志输出了2次,注意输出的logger name,虽然有一个是rootLogger的handler输出的,但是name还是子logger的。

2、有人可能会说,上面这个情况不太可能会发生,因为我直接设置一个子logger就ok了,是这样没错,but,当你直接使用logging.basicConfig对rootLogger对象进行配置或者在创建自己的logger对象之前,用logging.info等命令输出过日志的时候,logging会自动的(偷偷摸摸的)给你创建一个streamhandler,如下:

>>> root = logging.getLogger()
>>> root.handlers
[]
>>> logging.basicConfig(level=logging.INFO)
>>> root.handlers
[<StreamHandler <stderr> (NOTSET)>]
>>> root = logging.getLogger()
>>> root.handlers
[]
>>> logging.info('i am root')
>>> root.handlers
[<StreamHandler <stderr> (NOTSET)>]

现在明白为什么有时候创建了一个logger以后,会莫名其妙重复多次输出日志了吧?

3、还有一种情况,也堪称巨坑。当使用IDE工具编程的时候,整个IDE会话不结束,logger的handlers列表不清空!这就导致反复运行程序的时候,handler会反复添加,结果你会发现log输出越来越多。。。。。如下:



第一次运行,一个handler,第二次运行,变2个了,怎么办呢?可以在程序结尾加一行代码root.removeHandler(handler),或者简单粗暴一点,直接root.handlers=[],每次程序运行完,将handlers列表全清空。

差不多就这些吧,最后还有一个小知识点,有些同学会问,为啥我自定义的logger啥handler都没加的时候,也有输出呢?比如:

>>> import logging
>>> logger = logging.getLogger('child')
>>> logger.handlers
[]
>>> logging.root.handlers
[]
>>> logger.warning('i am child')
i am child

因为logging模块有一个默认的hanlder,可以通过logging.lastResort查看,如下:

>>> logging.lastResort
<_StderrHandler <stderr> (WARNING)>

这个handler没有和任何logger关联,专门处理用户啥都没配置的情况,可见,默认级别是warning,默认输出是stderr(注意是stderr而不是标准的stdout,因此如果你采取这种方式输出日志又进行了重定向,很有可能达不到想要的效果),仅仅输出一个message,其它啥都没有。

最后总结:logging确实很好用,但是个人觉得有些过于灵活了,很多事情私底下偷偷摸摸的做了,反而会给用户带来困扰。

原创不易,转载请注明出处。

====================================

2019年4月1日补充更新:

除了定制不同级别的level,logger还可以添加filter对象来对输出进行更加复杂的控制。filter和handler一样,先进行配置,然后再添加到logger对象中。所有日志在输出之前,都会先通过filter进行过滤。创建filter要先创建一个继承自logging.Filter的类,代码如下:

import logging
import sys class ContextFilter(logging.Filter):
def filter(self, record):
if record.role == "admin":
return True
else:
return False if __name__ == '__main__':
logger = logging.getLogger("Wechat")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(
'%(asctime)s %(levelname)s:%(message)s Role:%(role)s')
handler.setFormatter(formatter)
logger.addHandler(handler) f = ContextFilter()
logger.addFilter(f)
logger.info('An info message with %s', 'some parameters',
extra={"role": "admin"})
logger.info('An info message with %s', 'some parameters',
extra={"role": "hacker"})

从3.2以后,不一定非要创建一个logging.Filter子类,只要是有filter属性的任何对象或者函数都行,后台会检查这个对象是否有filter属性,如果有,则会调用这个对象的filter()方法,没有的话就把它当作一个callable对象,直接执行,并且把records当作一个参数传递给这个callable对象。

import logging
import sys def myfilter(record):
if record.role == "admin":
return True
else:
return False if __name__ == '__main__':
logger = logging.getLogger("Wechat")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s Role: %(role)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.addFilter(myfilter)
logger.info('An info message with %s', 'some parameters',
extra={"role":"admin"})
logger.info('An info message with %s', 'some parameters',
extra={"role":"hacker"}

部分代码参考https://www.cnblogs.com/progor/p/9269230.html,特此申明。

4.5日新增:

继续谈谈filter,需要注意的是,如果使用上面的例子,那么在每一条日志记录里面,都需要加上一条extra={"role":"xxx"},因为所有的日志record对象都会先通过filter过滤,此时会检查record是否有role属性,如果没有设置,显然会报错。

其实除了过滤这一种用法,filter还可以通过在函数里面给record添加属性,方便的增加自定义的字段。还是看例子:

def myfilter(record):
record.user = 'telecomshy'
return True >>> logger = logging.getLogger()
>>> logger.addFilter(myfilter)
>>> import sys
>>> fmt = logging.Formatter("%(levelname)s-%(asctime)s-%(message)s-%(user)s")
>>> handler = logging.StreamHandler(sys.stdout)
>>> handler.setLevel("DEBUG")
>>> handler.setFormatter(fmt)
>>> logger.addHandler(handler)
>>> logger.setLevel("DEBUG")
>>> logger.info('hello world')
INFO-2019-04-05 11:55:28,764-hello world-telecomshy

如上,通过使用filter,现在可以在formatter里面使用'user'这个自定义的字段了。

关于logging的那些坑的更多相关文章

  1. Kubernetes Fluentd+Elasticsearch+Kibana统一日志管理平台搭建的填坑指南

    在初步完成Kubernetes集群架构的建立后,通过搭建一些监控组件,我们已经能够实现 图形化的监控每个node,pod的状态信息和资源情况 通过scale进行replicateSet的扩展和伸缩 通 ...

  2. python 中 logging 模块的 log 函数以及坑

    记录下吧,一个日志的函数,但有个坑是在调用函数时需要先将函数实例化为一个变量,否则进入某个循环时会多次刷新日志: """ 日志模块 """ ...

  3. 【踩坑记录】记录一次使用Python logging库多进程打印日志的填坑过程

    背景: 项目使用Python自带的logging库来打印日志 项目部署在一台Centos7的机器上 项目采用gunicorn多进程部署 过程: 1.LOG日志代码封装: 采用logging库,并设置w ...

  4. 采坑复盘:logging日志能用封装后的函数来打日志,发现filename一直显示封装logging函数的方法所在的文件名

    问题: logging日志能用封装后的函数来打日志,发现filename一直显示封装logging函数的方法所在的文件名 原因: logging记录的是第一个函数执行所在的文件,那用封装的函数,首先执 ...

  5. Python logging模块日志存储位置踩坑

    问题描述 项目过程中写了一个小模块,设计到了日志存储的问题,结果发现了个小问题. 代码结构如下: db.py run.py 其中db.py是操作数据库抽象出来的一个类,run.py是业务逻辑代码.两个 ...

  6. node踩坑之This is probably not a problem with npm. There is likely additional logging output above.错误

    可能由于种种版本更新的原因需要执行 npm install重新安装一次,如果还是不可以的话,在把之前装的都清空 rm -rf node_modulesrm package-lock.jsonnpm c ...

  7. Python Logging模块的简单使用

    前言 日志是非常重要的,最近有接触到这个,所以系统的看一下Python这个模块的用法.本文即为Logging模块的用法简介,主要参考文章为Python官方文档,链接见参考列表. 另外,Python的H ...

  8. java.logging的重定向?

    接着昨天的工作. 上面说要重定向java.util.logging.Logger的输出, 发现也不是不可能. package jmx; import java.util.logging.FileHan ...

  9. Python中的logging模块

    http://python.jobbole.com/86887/ 最近修改了项目里的logging相关功能,用到了python标准库里的logging模块,在此做一些记录.主要是从官方文档和stack ...

随机推荐

  1. ResponseUtil

    package util; import java.io.OutputStream; import java.io.PrintWriter; import javax.servlet.http.Htt ...

  2. Hadoop IO基于文件的数据结构详解【列式和行式数据结构的存储策略】

    Charles所有关于hadoop的文章参考自hadoop权威指南第四版预览版 大家可以去safari免费阅读其英文预览版.本人也上传了PDF版本在我的资源中可以免费下载,不需要C币,点击这里下载. ...

  3. ssh免密连接远程服务器

    ssh免密连接远程服务器 借助ssky-keygen和ssh-copy-id工具,通过4个简单的步骤实现无需输入密码登录远程Linux主机 1 生成密钥 通过内置的工具生成RSA算法加密的密钥 ssh ...

  4. 如何解决quartz在集群下出现的资源抢夺现象

    Quartz是一个开源的作业调度框架,它完全由Java写成,并设计用于J2SE和J2EE应用中.它提供了巨大的灵活性而不牺牲简单性.你能够用它来为执行一个作业而创建简单的或复杂的调度,简单的说就是可以 ...

  5. 201671010140. 2016-2017-2 《Java程序设计》java学习第一周

       java学习第一周        本周是新学期的开端,也是新的学习进程的开端,第一次接触java这门课程,首先书本的厚度就给我一种无形的压力,这注定了,这门课程不会是轻松的,同时一种全新的学习方 ...

  6. 【bzoj1455】罗马游戏

    1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1061  Solved: 439[Submit][Status][Discuss] ...

  7. JS中的两种刷新方法以及区别和适用范围

    在项目中有一个人信息修改的页面,但是修改后显示的却是修改之前的内容,分析问题后发现查询语句写在了修改语句之前,有些某些需要又必须这么写,但是修改信息后先却显示之前的信息也太不科学了. 所以我就想用js ...

  8. POI技术

    1.excel左上角有绿色小图标说明单元格格式不匹配 2.模板中设置自动计算没效果,需要加上sheet.setForceFormulaRecalculation(true); FileInputStr ...

  9. java就业指南 zookeeper分布式系统 zookeeper实现分布式锁 有用

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个 分布式系统都无法同时满足一致性(Consistency).可用性 ...

  10. PCL点云库中的坐标系(CoordinateSystem)

    博客转载自:https://blog.csdn.net/qq_33624918/article/details/80488590 引言 世上本没有坐标系,用的人多了,便定义了坐标系统用来定位.地理坐标 ...