python logger 打印多次的问题与解决方案

学习 Python 不久碰到过这个问题, 记得当时没查出是什么问题. 刚刚无意中发现了这个问题的原因及解决方案, 记录一下.

参考:
https://juejin.im/post/5bc2bd3a5188255c94465d31

第一种情况

参考文章中介绍说产生这个问题的原因是因为创建了自定义的 Logger 对象后, 又使用了 logging 中的日志输出方法,这些方法使用的是默认配置的 Logger 对象,导致之后输出的日志信息会重复。

示例代码:

import logging

# 日志管理
logger = logging.getLogger('logger')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
# 输出到控制台的log
sh = logging.StreamHandler()
sh.setLevel(logging.DEBUG)
sh.setFormatter(formatter)
logger.addHandler(sh)

logger.debug('a debug message')
logger.info('a info message')
logger.warning('a warning message')
logging.error('a error message')
logger.critical('a critical message')

打印结果如下:

2019-07-17 09:42:29,336 spider.py[line:92] DEBUG a debug message
2019-07-17 09:42:29,336 spider.py[line:93] INFO a info message
2019-07-17 09:42:29,336 spider.py[line:94] WARNING a warning message
ERROR:root:a error message
2019-07-17 09:42:29,336 spider.py[line:96] CRITICAL a critical message
CRITICAL:logger:a critical message
[Finished in 0.6s]

可以看到从 error message 开始出现格式异常, 之后下一条 message 开始重复打印. 之后每一条 log 都会打印出两次. 去掉使用 logging 的日志输出方法后则打印正常.

第二种情况

封装了 logger 到文件后在其他文件 import 引用这个 logger . 却又在文件中创建了新的 logger . 如下:

封装了 loggerbase_utils:

import configparser
import logging
import time
import os

# 配置文件管理
confLoad = configparser.ConfigParser()
confLoad.read('./spider.conf')

# 日志管理
logger = logging.getLogger('logger')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
# 输出到log目录下的当日log文件
log_dir = confLoad.get('logger','LOG_DIR')
if not os.path.exists(log_dir):
    os.makedirs(log_dir)
logFile = log_dir + time.strftime("%Y-%m-%d", time.localtime()) + ".log"
fh = logging.FileHandler(logFile, encoding='utf-8')
fh.setLevel(int(confLoad.get('logger','LOGFILE_LEVEL')))
fh.setFormatter(formatter)
# 输出到控制台的log
sh = logging.StreamHandler()
sh.setLevel(int(confLoad.get('logger','CONSOLE_LEVEL')))
sh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(sh)

配置文件 spider.conf :

# mongodb
[mongodb]
DB_MONGO_HOST=192.168.0.101
DB_MONGO_PORT=27017
DB_MONGO_DATABASE=spider
DB_MONGO_USERNAME=
DB_MONGO_PASSWORD=

[logger]
LOG_DIR = ./log/

# CRITICAL = 50
# FATAL = CRITICAL
# ERROR = 40
# WARNING = 30
# WARN = WARNING
# INFO = 20
# DEBUG = 10
# NOTSET = 0

CONSOLE_LEVEL = 10
LOGFILE_LEVEL = 10

在文件 spider.py 中引用:

from base_utils import logger

if __name__ == "__main__":
	# 如果在此文件中再次创建 logger, 会产生两个 logger 对象, 使 log 打印 2 次
    logger = logging.getLogger('logger')
    logger.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
	sh = logging.StreamHandler()
    sh.setLevel(logging.DEBUG)
    sh.setFormatter(formatter)
    logger.addHandler(sh)

	logger.debug('a debug message')
    logger.info('a info message')
    logger.warning('a warning message')
    logger.error('a error message')
    logger.critical('a critical message')

打印结果如下:

2019-07-17 10:01:21,969 spider.py[line:93] DEBUG a debug message
2019-07-17 10:01:21,969 spider.py[line:93] DEBUG a debug message
2019-07-17 10:01:21,970 spider.py[line:94] INFO a info message
2019-07-17 10:01:21,970 spider.py[line:94] INFO a info message
2019-07-17 10:01:21,970 spider.py[line:95] WARNING a warning message
2019-07-17 10:01:21,970 spider.py[line:95] WARNING a warning message
2019-07-17 10:01:21,971 spider.py[line:96] ERROR a error message
2019-07-17 10:01:21,971 spider.py[line:96] ERROR a error message
2019-07-17 10:01:21,971 spider.py[line:97] CRITICAL a critical message
2019-07-17 10:01:21,971 spider.py[line:97] CRITICAL a critical message
[Finished in 0.6s]

可以看到 log 打印了 2 次, 同样的要避免这样子使用.

<think>好的,用户现在想知道如何将Python打印到终端的内容同时保存到日志文件中。我需要详细思考这个问题,并给出清晰的解决方案。首先,用户可能已经使用print语句在代码中输出了很多信息,现在希望这些信息不仅显示在终端,还要记录到日志文件里,方便后续查看或调试。 首先,我应该考虑Python中的标准输出重定向。用户可能对编程有一定了解,但可能不熟悉日志模块或者文件操作。我需要解释两种主要方法:一种是重定向sys.stdout,另一种是使用logging模块同时输出到终端和文件。 对于第一种方法,重定向sys.stdout,这是一个比较直接的方式,但需要用户理解标准输出的概念。用户可以创建一个类,同时将输出发送到原stdout和文件。这样,所有print语句的内容都会同时出现在终端和日志中。但需要注意,这种方法可能会捕获所有标准输出,包括其他库的输出,这可能是用户想要的,也可能不是。此外,用户需要处理文件的打开和关闭,以及可能的异常情况,比如文件无法写入时的错误处理。 第二种方法是使用logging模块。这可能更符合Python的最佳实践,尤其是对于需要更复杂日志管理的用户。logging模块可以配置多个处理器,比如同时输出到终端和文件。用户需要创建logger,添加StreamHandler和FileHandler,并设置适当的日志级别。这样,用户可以将print语句替换为logger.info()等调用,但这也意味着需要修改现有的代码,将print换成logging语句。不过,这种方法提供了更多的灵活性,比如可以设置不同的日志级别,格式化输出,以及按时间或大小轮换日志文件。 接下来,我需要比较这两种方法的优缺点。sys.stdout的重定向方法简单,不需要修改现有的print语句,适合快速实现需求,但可能不够灵活,无法区分不同级别的日志信息,或者处理多线程/多进程的情况。而logging模块虽然需要更多的设置和代码改动,但提供了更强大的功能,适合长期维护的项目。 用户可能关心如何选择这两种方法。如果用户只是想快速将现有的print输出同时记录到文件,而不想改动代码,那么重定向sys.stdout是更好的选择。但如果有计划进行更详细的日志记录,或者需要分级别记录信息,那么使用logging模块会更合适。 此外,还需要考虑编码和缓冲的问题。例如,在重定向sys.stdout时,确保文件以正确的编码打开,并且及时刷新缓冲区,避免日志内容没有及时写入文件。在示例代码中,使用open函数时指定encoding='utf-8'是个好习惯,尤其是在处理中文或其他非ASCII字符时。同时,在写入文件后调用flush()方法可以确保内容立即写入,而不是等到缓冲区满。 对于logging模块的示例,需要说明如何配置格式,比如添加时间戳、日志级别等信息,这样日志文件会更具有可读性。用户可能希望日志中包含更多上下文信息,而不仅仅是print输出的内容,这时候logging的优势就体现出来了。 另外,可能需要提醒用户,如果同时使用print和logging,可能会有重复的输出,需要确保两者不会冲突。比如,如果同时重定向sys.stdout到文件和添加StreamHandler,可能会导致终端输出两次。因此,建议用户根据需求选择一种方法,而不是混合使用。 最后,总结两种方法的适用场景,帮助用户做出选择。可能用户是开发者,需要快速实现功能,或者是在维护一个已有项目,不想大量修改代码,这时候sys.stdout的方法更合适。如果是新项目,或者需要更结构化的日志记录,推荐使用logging模块。 在编写回答时,需要确保步骤清晰,代码示例正确,并且解释每个步骤的作用。同时,提醒用户注意潜在的问题,比如文件权限、路径存在性,以及异常处理等。例如,在文件操作时,需要确保目标目录存在,否则会抛出异常。用户可能需要添加try-except块来处理这些情况,但为了示例简洁,可能暂时省略,但在实际应用中需要注意。 总之,我需要提供一个结构清晰、分步骤的指南,涵盖两种主要方法,并比较它们的优缺点,帮助用户根据具体情况选择最合适的解决方案。</think>在 Python 中实现终端输出同时写入日志文件,可通过以下两种主要方法实现: --- ### **方法一:重定向标准输出 (推荐快速实现)** #### 核心原理:同时向终端和文件写入 ```python import sys class TeeLogger: def __init__(self, filename): self.file = open(filename, 'a', encoding='utf-8') # 追加模式 self.stdout = sys.stdout # 保存原终端输出 def write(self, text): # 同时写入终端和文件 self.stdout.write(text) # 终端输出 self.file.write(text) # 文件写入 self.flush() # 实时刷新 def flush(self): # 必须实现该方法 self.stdout.flush() self.file.flush() # 使用示例 sys.stdout = TeeLogger('output.log') # 全局重定向 print("这条内容会同时显示在终端和日志文件") # 所有print自动记录 ``` #### **特点** - ✅ 零修改现有 `print` 语句 - ✅ 自动捕获所有标准输出(包括第三方库的打印) - ⚠️ 需自行处理异常和文件关闭(可添加 `__del__` 方法自动关闭) --- ### **方法二:使用 logging 模块 (推荐专业场景)** #### 配置同时输出到终端和文件 ```python import logging # 创建日志器 logger = logging.getLogger('dual_logger') logger.setLevel(logging.DEBUG) # 设置最低日志级别 # 创建终端处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 终端显示INFO及以上级别 # 创建文件处理器 file_handler = logging.FileHandler('app.log', encoding='utf-8') file_handler.setLevel(logging.DEBUG) # 文件记录DEBUG及以上级别 # 设置日志格式 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') console_handler.setFormatter(formatter) file_handler.setFormatter(formatter) # 添加处理器 logger.addHandler(console_handler) logger.addHandler(file_handler) # 使用示例 logger.info("业务信息:用户登录成功") # 同时输出到终端和文件 logger.debug("调试信息:变量X=42") # 仅写入文件 ``` #### **特点** - ✅ 专业级日志分级控制(DEBUG/INFO/WARNING 等) - ✅ 支持日志轮转、格式化等高级功能 - ✅ 可通过 `logging.basicConfig()` 快速配置 - ⚠️ 需将 `print` 替换为 `logger.info()` --- ### **方法对比** | 特性 | 标准输出重定向 | logging 模块 | |---------------------|-------------------------|---------------------------| | 代码侵入性 | 无(直接替换sys.stdout)| 需替换print为logger调用 | | 输出控制粒度 | 所有标准输出统一处理 | 可分级控制不同内容 | | 多目标输出 | 需自定义实现 | 原生支持多个Handler | | 异常处理 | 需手动管理 | 自动处理 | | 适合场景 | 快速实现/简单脚本 | 长期项目/专业日志管理 | --- ### **实用技巧** 1. **混合使用两种方法**(需避免重复输出): ```python # 在logging配置中同时添加StreamHandler和FileHandler # 然后用logger替代print即可 ``` 2. **临时恢复标准输出**: ```python original_stdout = sys.stdout # 备份 sys.stdout = TeeLogger('log.txt') # 重定向 # ... 你的代码 ... sys.stdout = original_stdout # 恢复 ``` 3. **实时查看日志文件**: ```bash tail -f output.log # Linux/Mac 实时监控日志 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值