关于logging的那些坑
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的那些坑的更多相关文章
- Kubernetes Fluentd+Elasticsearch+Kibana统一日志管理平台搭建的填坑指南
在初步完成Kubernetes集群架构的建立后,通过搭建一些监控组件,我们已经能够实现 图形化的监控每个node,pod的状态信息和资源情况 通过scale进行replicateSet的扩展和伸缩 通 ...
- python 中 logging 模块的 log 函数以及坑
记录下吧,一个日志的函数,但有个坑是在调用函数时需要先将函数实例化为一个变量,否则进入某个循环时会多次刷新日志: """ 日志模块 """ ...
- 【踩坑记录】记录一次使用Python logging库多进程打印日志的填坑过程
背景: 项目使用Python自带的logging库来打印日志 项目部署在一台Centos7的机器上 项目采用gunicorn多进程部署 过程: 1.LOG日志代码封装: 采用logging库,并设置w ...
- 采坑复盘:logging日志能用封装后的函数来打日志,发现filename一直显示封装logging函数的方法所在的文件名
问题: logging日志能用封装后的函数来打日志,发现filename一直显示封装logging函数的方法所在的文件名 原因: logging记录的是第一个函数执行所在的文件,那用封装的函数,首先执 ...
- Python logging模块日志存储位置踩坑
问题描述 项目过程中写了一个小模块,设计到了日志存储的问题,结果发现了个小问题. 代码结构如下: db.py run.py 其中db.py是操作数据库抽象出来的一个类,run.py是业务逻辑代码.两个 ...
- 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 ...
- Python Logging模块的简单使用
前言 日志是非常重要的,最近有接触到这个,所以系统的看一下Python这个模块的用法.本文即为Logging模块的用法简介,主要参考文章为Python官方文档,链接见参考列表. 另外,Python的H ...
- java.logging的重定向?
接着昨天的工作. 上面说要重定向java.util.logging.Logger的输出, 发现也不是不可能. package jmx; import java.util.logging.FileHan ...
- Python中的logging模块
http://python.jobbole.com/86887/ 最近修改了项目里的logging相关功能,用到了python标准库里的logging模块,在此做一些记录.主要是从官方文档和stack ...
随机推荐
- html5调用本机摄像头兼容谷歌浏览器高版本,谷歌浏览器低版本,火狐浏览器
做这个功能的时候在网上查了一些资料,代码如下,在这个代码在谷歌浏览器46版本是没问题的,在火狐浏览器也行,但是在谷歌浏览器高版本下是不兼容的 <div id="body"&g ...
- uboot重定位代码分析(转)
概述 重定位(relocate)代码将BootLoader自身由Flash复制到SDRAM,以便跳转到SDRAM执行.之所以需要进行重定位是因为在Flash中执行速度比较慢,而系统复位后总是从0x00 ...
- Android剖析和运行机制
Android剖析和运行机制 大纲: 1. Android剖析 Linux内核本地库(Native Libraries)Android运行时(Android Runtime)应用框架 2. Andro ...
- 正确理解Python函数是第一类对象
正确理解 Python函数,能够帮助我们更好地理解 Python 装饰器.匿名函数(lambda).函数式编程等高阶技术. 函数(Function)作为程序语言中不可或缺的一部分,太稀松平常了.但函数 ...
- Android P2P语音通话实现
1.http://www.cnblogs.com/milospooner/archive/2012/07/13/2590950.html 2.http://my.oschina.net/sanshan ...
- Spring总结一:Srping快速入门
Sping是什么: Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的J ...
- zookeeper集群安装的奇怪现象
zookeeper:配置的集群信息是domain:端口2888:端口3888: domain为内网静态ip:每次启动都不能相互连接报错误: [myid:3] - WARN [WorkerSende ...
- UVA-11280 Flying to Fredericton
题意 给定一些国家,和两个国家间的花费,现在有一些询问,询问每次最多转k次飞机,最小花费 分析 最短路的裸题,跑spfa或者dijsktra什么的都行 多开一维来记录转k次飞机时的最短路是什么(拆点? ...
- 面试题:Java多线程必须掌握的十个问题 背1
一.进程与线程?并行与并发? 进程代表一个运行中的程序,是资源分配与调度的基本单位.进程有三大特性: 1.独立性:独立的资源,私有的地址空间,进程间互不影响. 2.动态性:进程具有生命周期. 3.并发 ...
- Luogu 4251 [SCOI2015]小凸玩矩阵
BZOJ 4443 二分答案 + 二分图匹配 外层二分一个最小值,然后检验是否能选出$n - k + 1$个不小于当前二分出的$mid$的数.对于每一个$a_{i, j} \geq mid$,从$i$ ...