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

2024-06-07 18:48

本文主要是介绍Python | 解决方案 | 多个文件共用logger,重复打印问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

项目中封装了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):'''指定保存日志的文件路径,日志级别,以及调用文件将日志存入到指定的文件中'''# 创建一个loggerself.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添加handlerself.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 Loggerlogger = 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.loggerRootLogger
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对象
    • 不推荐
  2. 每次logger输出之后,移除FileHandler对象
    • 重写输出接口,输出后增加removeHandler操作
    • 繁琐,复杂,不推荐
  3. 通过判断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):'''指定保存日志的文件路径,日志级别,以及调用文件将日志存入到指定的文件中'''# 创建一个loggerself.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添加handlerself.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模块浅析

这篇关于Python | 解决方案 | 多个文件共用logger,重复打印问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040000

相关文章

Python判断for循环最后一次的6种方法

《Python判断for循环最后一次的6种方法》在Python中,通常我们不会直接判断for循环是否正在执行最后一次迭代,因为Python的for循环是基于可迭代对象的,它不知道也不关心迭代的内部状态... 目录1.使用enuhttp://www.chinasem.cnmerate()和len()来判断for

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

使用Python合并 Excel单元格指定行列或单元格范围

《使用Python合并Excel单元格指定行列或单元格范围》合并Excel单元格是Excel数据处理和表格设计中的一项常用操作,本文将介绍如何通过Python合并Excel中的指定行列或单... 目录python Excel库安装Python合并Excel 中的指定行Python合并Excel 中的指定列P

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

数据库oracle用户密码过期查询及解决方案

《数据库oracle用户密码过期查询及解决方案》:本文主要介绍如何处理ORACLE数据库用户密码过期和修改密码期限的问题,包括创建用户、赋予权限、修改密码、解锁用户和设置密码期限,文中通过代码介绍... 目录前言一、创建用户、赋予权限、修改密码、解锁用户和设置期限二、查询用户密码期限和过期后的修改1.查询用