项目中封装了logging库为log.py,实现既把日志输出到控制台, 又写入日志文件文件。

环境:python3.7.3

项目中,多个文件共用logger,出现重复打印问题,解决流程记录如下:

文件和调用方式如下:

log.py v1

#encoding = utf-8
###
# @ Description: 日志封装文件
# @ Author: fatih
# @ Date: 2020-12-30 10:48:00
# @ FilePath: \mechineInfo\utils\log.py
# @ LastEditors: fatih
# @ LastEditTime: 2021-01-11 16:18:30
###
import logging
# 既把日志输出到控制台, 还要写入日志文件
class Logger():
def __init__(self, logname="info", loglevel=logging.DEBUG, loggername=None):
'''
指定保存日志的文件路径,日志级别,以及调用文件
将日志存入到指定的文件中
'''
# 创建一个logger
self.logger = logging.getLogger(loggername)
self.logger.setLevel(loglevel)
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler(logname)
fh.setLevel(loglevel)
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(loglevel)
# 定义handler的输出格式
# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
formatter = logging.Formatter('[%(levelname)s]%(asctime)s %(filename)s:%(lineno)d: %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
self.logger.addHandler(fh)
self.logger.addHandler(ch) #测试日志,正式环境可删除
self.logger.fatal("set logger")
def getlog(self):
self.logger.fatal("get logger")
return self.logger

问题一:多文件调用共用logger,重复打印

调用方式:

a.py v1

#!/usr/bin/python
# -*- coding:utf-8 -*- import logging
from log import Logger logger = Logger().getlog()
logger.debug("this is a")

b.py v1

#!/usr/bin/python
# -*- coding:utf-8 -*-
import logging
from log import Logger
import a
logger = Logger().getlog()
logger.debug("this is b")

此时执行b.py的结果如下:

$ python3 b.py
[CRITICAL]2021-01-20 10:51:17,966 log.py:44: set logger
[CRITICAL]2021-01-20 10:51:17,966 log.py:47: get logger
[DEBUG]2021-01-20 10:51:17,967 a.py:7: this is a
[CRITICAL]2021-01-20 10:51:17,968 log.py:44: set logger
[CRITICAL]2021-01-20 10:51:17,968 log.py:44: set logger
[CRITICAL]2021-01-20 10:51:17,968 log.py:47: get logger
[CRITICAL]2021-01-20 10:51:17,968 log.py:47: get logger
[DEBUG]2021-01-20 10:51:17,969 b.py:8: this is b
[DEBUG]2021-01-20 10:51:17,969 b.py:8: this is b

从结果可以看出来,存在logger共用,导致重复打印。

原因分析

调用方式logger = Logger().getlog(),即self.logger = logging.getLogger(logger)

logger参数并未传递,所以得到的self.logger是RootLogger

而RootLogger是一个python程序内全局唯一的,所有Logger对象的祖先。

也就是说先打开的文件中对log的设置,后打开的文件都会受到影响,都会走一遍logger的继承关系。

b.py import a, 先执行a, 然后调用getLogger,得到RootLogger,打印一次,再执行一次在a.py中打开的RootLogger,再打印一次

解决方案

不同文件调用,使用不同的loggername

a.py v2

logger = Logger("a.log", logging.DEBUG, __name__).getlog()
logger.debug("this is a")

b.py v2

logger = Logger("b.log", logging.DEBUG, __name__).getlog()
logger.debug("this is b")

此时重新执行

命令行显示正常了

[CRITICAL]2021-01-20 11:02:22,763 log.py:44: set logger
[CRITICAL]2021-01-20 11:02:22,764 log.py:47: get logger
[DEBUG]2021-01-20 11:02:22,764 a.py:7: this is a
[CRITICAL]2021-01-20 11:02:22,765 log.py:44: set logger
[CRITICAL]2021-01-20 11:02:22,765 log.py:47: get logger
[DEBUG]2021-01-20 11:02:22,765 b.py:8: this is

问题二:单文件重复调用logger,重复打印

a.py v2

logger = Logger("a.log", logging.DEBUG, __name__).getlog()
logger.debug("this is a")
logger = Logger("a.log", logging.DEBUG, __name__).getlog()
logger.debug("this is a 2")

打印结果:

[CRITICAL]2021-01-20 11:39:04,312 log.py:44: set logger
[CRITICAL]2021-01-20 11:39:04,312 log.py:47: get logger
[DEBUG]2021-01-20 11:39:04,312 a.py:7: this is a
[CRITICAL]2021-01-20 11:39:04,313 log.py:44: set logger
[CRITICAL]2021-01-20 11:39:04,313 log.py:44: set logger
[CRITICAL]2021-01-20 11:39:04,313 log.py:47: get logger
[CRITICAL]2021-01-20 11:39:04,313 log.py:47: get logger
[DEBUG]2021-01-20 11:39:04,313 a.py:10: this is a 2
[DEBUG]2021-01-20 11:39:04,313 a.py:10: this is a 2

此时例如执行logger = Logger("a.log", logging.DEBUG, name).getlog(),并且后续日志对象的第三个传参都相同的时候,

此后多次打印日志会出现日志信息条数线性增加

例如第一次打印一条,第二条打印相同的两条日志,第三次打印相同的三条日志…

原因解析

因为logger的name被固定,

所以当你第一次为logger对象添加FileHandler对象之后,

如果没有移除上一次的FileHandler对象,第二次logger对象就会再次获得相同的FileHandler对象,即拥有两个FileHandler对象,

最终造成打印两次,

同样,如果此时没有立即移除上一次的FileHandler对象,第三次logger对象就会再次获得相同的FileHandler对象,即拥有三个FileHandler象,最终打印3次…

解决方案

  1. 每次添加日志,创建与上次日志对象的loggername属性不同的logger对象

    • 调用会导致同时存在超多logger对象
    • 不推荐
    1. 每次logger输出之后,移除FileHandler对象
    • 重写输出接口,输出后增加removeHandler操作
    • 繁琐,复杂,不推荐
  2. 通过判断logger对象的handlers属性,或者hasHandlers函数,保持同一loggername对应的FileHander唯一
    • 只需要增加一行代码,if not logger.handlers: 或if not self.logger.hasHandlers()只有不存在Handler时才设置Handler
    • 简洁,推荐

第三种方案,改写log.py v1代码如下

log.py v2

#encoding = utf-8
###
# @ Description: 日志封装文件
# @ Author: fatih
# @ Date: 2020-12-30 10:48:00
# @ FilePath: \mechineInfo\utils\log.py
# @ LastEditors: fatih
# @ LastEditTime: 2021-01-11 16:18:30
###
import logging
# 既把日志输出到控制台, 还要写入日志文件
class Logger():
def __init__(self, logname="info", loglevel=logging.DEBUG, loggername=None):
'''
指定保存日志的文件路径,日志级别,以及调用文件
将日志存入到指定的文件中
'''
# 创建一个logger
self.logger = logging.getLogger(loggername)
self.logger.setLevel(loglevel)
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler(logname)
fh.setLevel(loglevel)
if not self.logger.handlers:
#或者使用如下语句判断
#if not self.logger.hasHandlers(): # 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(loglevel)
# 定义handler的输出格式
# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
formatter = logging.Formatter('[%(levelname)s]%(asctime)s %(filename)s:%(lineno)d: %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
self.logger.addHandler(fh)
self.logger.addHandler(ch) self.logger.fatal("add handler")
self.logger.fatal("set logger")
def getlog(self):
self.logger.fatal("get logger")
return self.logger

此时调用a.py v2

结果如下:

[CRITICAL]2021-01-20 11:55:08,427 log.py:44: add handler
[CRITICAL]2021-01-20 11:55:08,427 log.py:46: set logger
[CRITICAL]2021-01-20 11:55:08,427 log.py:49: get logger
[DEBUG]2021-01-20 11:55:08,427 a.py:7: this is a
[CRITICAL]2021-01-20 11:55:08,428 log.py:46: set logger
[CRITICAL]2021-01-20 11:55:08,428 log.py:49: get logger
[DEBUG]2021-01-20 11:55:08,429 a.py:10: this is a 2

至此,问题解决

参考:

python的logging模块浅析

来源:https://blog.csdn.net/u011417820/article/details/112861970

Python | 解决方案 | 多个文件共用logger,重复打印问题的更多相关文章

  1. 【python】多个文件共用日志系统的重复打印问题

    先写一个最简单的log文件: test_logging5.py #coding:utf-8 import logging logging.debug('logger debug message') l ...

  2. Python脚本:删除文件夹下的重复图片,实现图片去重

    近期在整理相册的时候,发现相册中有许多重复图片,人工一张张筛查删除太枯燥,便写下这个脚本,用于删除文件夹下重复的图片. 第一部分:判断两张图片是否相同 要查找重复的图片,必然绕不开判断两张图片是否相同 ...

  3. python十行代码实现文件去重,去除重复文件的脚本

    导入依赖 '''导入依赖''' from pathlib import Path import filecmp 函数说明 ''' filecmp.cmp(path1, path2, shallow=T ...

  4. python 函数初识和文件操作

    文件操作  打开文件:文件句柄 = open('文件路径', '模式')  打开文件的模式 w #以写的方式打开 (不可读,不存在则创建,存在则删除内容) a #以追加的模式打开(可读, 不存在则创建 ...

  5. 第三章:Python基础の函数和文件操作实战

    本課主題 Set 集合和操作实战 函数介紹和操作实战 参数的深入介绍和操作实战 format 函数操作实战 lambda 表达式介绍 文件操作函数介紹和操作实战 本周作业 Set 集合和操作实战 Se ...

  6. python字符编码和文件处理

    一.了解字符编码的知识储备 1.文本编辑器存取文件的原理(nodepad++,python,word) 打开编辑器就打开了启动了一个进程,是在内存中的,所以,用编辑器编写的内容也都是存放于内存中的,断 ...

  7. python基础3之文件操作、字符编码解码、函数介绍

    内容概要: 一.文件操作 二.字符编码解码 三.函数介绍 一.文件操作 文件操作流程: 打开文件,得到文件句柄并赋值给一个变量 通过句柄对文件进行操作 关闭文件 基本操作: #/usr/bin/env ...

  8. python基础——元组、文件及其它

    Python核心数据类型--元组 元组对象(tuple)是序列,它具有不可改变性,和字符串类似.从语法上讲,它们便在圆括号中,它们支持任意类型.任意嵌套及常见的序列操作. 任意对象的有序集合:与字符串 ...

  9. Python编程-编码、文件处理、函数

    一.字符编码补充知识点 1.文本编辑器存取文件的原理(nodepad++,pycharm,word) 打开编辑器就打开了启动了一个进程,是在内存中的,所以在编辑器编写的内容也都是存放与内存中的,断电后 ...

  10. [原创]使用python对视频/音频文件进行详细信息采集,并进行去重操作

    [原创]使用python对视频/音频文件进行详细信息采集,并进行去重操作 转载请注明出处 一.关于为什么用pymediainfo以及pymediainfo的安装 使用python对视频/音频文件进行详 ...

随机推荐

  1. CSS操作——display属性

    display可以指定元素的显示模式,它可以把行内元素修改成块状元素,也可以把别的模式的元素改成行内元素.diisplay常用的值有四个. 语法: /* display: block; // 声明当前 ...

  2. Android 13 - Media框架(7)- NuPlayer::Source

    关注公众号免费阅读全文,进入音视频开发技术分享群! Source 在播放器中起着拉流(Streaming)和解复用(demux)的作用,Source 设计的好坏直接影响到播放器的基础功能,我们这一节将 ...

  3. 面试题一《swift和oc的区别》

    一.来源 这道题来自网上一篇文章<100家公司iOS面试题管理>,这份题目虽然题目质量不高,但是覆盖面比较全,有学习的价值 二.解析 1.swift 比 OC更年轻,这意味着 swift ...

  4. itest(爱测试) 开源接口测试,敏捷测试管理平台10.0.0GA 发布

    一:itest work 简介 itest work 开源敏捷测试管理,包含极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试,接口Mock,还有压测 ,又有丰富的统计分析,8合1工作站.可按 ...

  5. ansible list错误

    [root@localhost ansible]# ansible all -list [WARNING]: * Failed to parse /etc/ansible/1.txt with ini ...

  6. Java中GUI

    目录 1.Java GUI 概述 2.容器 2.1 窗口 2.2 弹窗和对话框 对话框 自定义弹窗 2.3 面板 普通面板 滚动面板 分隔面板 选项卡面板 3.布局 3.1.流式布局 3.2.网格布局 ...

  7. c# Redis缓存的使用和helper类;

    使用背景: 项目中用户频繁访问数据库会导致程序的卡顿,甚至堵塞.使用缓存可以有效的降低用户访问数据库的频次,有效的减少并发的压力.保护后端真实的服务器. 对于开发人员需要方便调用,所以本文提供了hel ...

  8. k8s 安装ingress nginx controller 并部署.net core ingress服务

    k8s 安装ingress nginx controller 并部署.net core ingress服务 本地k8s集群概览 192.168.28.132 k8smaster 192.168.28. ...

  9. nginx location实战

    nginx location高级实战 location是nginx的核心重要功能,可以设置网站的访问路径,一个web server会有多个路径,那么location就得设置多个. Nginx的loca ...

  10. golang + postgresql + Kubernetes 后端学习

    记录 链接 dbdiagram 基于 Golang + PostgreSQL + Kubernetes 后端开发大师班[中英字幕]