关闭标准输出stdout 后,影响了自己的日志文件

本文探讨了在C/C++开发中如何有效管理标准输出,避免printf内容干扰日志文件。通过实例演示了如何利用dup/dup2及open函数重新定向输出流,确保程序输出清晰且不影响日志记录。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C/C++ 开发的经常会用printf cout 输出一些调试信息,如果将程序前台启动就会输出到控制台,自己写的代码还好,可以删掉,但有的时候用别人的库或者代码,带有大量的printf,程序一启动就会刷屏,就不能操控程序或者看不到自己想看的信息。这个适合可以用dup/dup2 复制一下标准输出,然后在关掉标准输出。
但最近调试程序的时候突然发现,关掉标注输出后,原本printf 输出的内容出现在了我的日志文件里,导致我的日志文件极速增长,并且很难看到原本我的日志输出,突然不知所以,觉得这个方法不香了,苦恼了好一会儿。艰难的调完程序后静下心来想这个事儿。应该是close(1)后,我日志文件的fd 刚好是1,导致问题的出现。因为close(1) 后fd 1 被回收,刚好日志文件是第一个打开的文件,恰恰使用了1。下面通过代码验证了这一猜测。

先看一下有问题的代码

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>

int fdout,fderr,fdlog;

int my_print (const char *__restrict __format, ...){
    char tmpstr[2048] = {0};
    va_list args;
    va_start(args,__format);
    int ret=vsnprintf(tmpstr,sizeof(tmpstr),__format,args);
    va_end(args);
    ret = write(fdout,tmpstr,strlen(tmpstr));
    return ret;
}


int my_log (const char *__restrict __format, ...){
    char tmpstr[2048] = {0};
    va_list args;
    va_start(args,__format);
    int ret=vsnprintf(tmpstr,sizeof(tmpstr),__format,args);
    va_end(args);
    ret = write(fdlog,tmpstr,strlen(tmpstr));
    return ret;
}


int main(int argc, char* argv[])
{
    fdout = dup(1);
    fderr = dup(2);
    dup2(1,fdout);
    dup2(2,fderr);
    close(1);
    close(2);

    fdlog = open("./mylog",O_CREAT|O_RDWR|O_APPEND);
    my_log("my_log out ..\n");

    printf("std printf out ...\n");

    my_print("my_print out ... \n");

    return 0;
}

查看程序输出
在这里插入图片描述
先复制1然后,close(1)后,my_print输出到了控制台,printf的输出不会再输出到控制台,但却出现在我打开的日志文件里。

用gdb 看一下fdlog的值
在这里插入图片描述
fdlog 的值确实是1。

下面对代码进行修改,打开null文件占用1和2

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>

int fdout,fderr,fdlog;

int my_print (const char *__restrict __format, ...){
    char tmpstr[2048] = {0};
    va_list args;
    va_start(args,__format);
    int ret=vsnprintf(tmpstr,sizeof(tmpstr),__format,args);
    va_end(args);
    ret = write(fdout,tmpstr,strlen(tmpstr));
    return ret;
}


int my_log (const char *__restrict __format, ...){
    char tmpstr[2048] = {0};
    va_list args;
    va_start(args,__format);
    int ret=vsnprintf(tmpstr,sizeof(tmpstr),__format,args);
    va_end(args);
    ret = write(fdlog,tmpstr,strlen(tmpstr));
    return ret;
}


int main(int argc, char* argv[])
{
    fdout = dup(1);
    fderr = dup(2);
    dup2(1,fdout);
    dup2(2,fderr);
    close(1);
    close(2);

    int fd_tmp1 = 0,fd_tmp2 = 0;
    // 打开null文件占用1和2
    fd_tmp1 = open("/dev/null",O_WRONLY);
    fd_tmp2 = open("/dev/null",O_WRONLY);

    fdlog = open("./mylog",O_CREAT|O_RDWR|O_APPEND);
    my_log("my_log out ..\n");

    printf("std printf out ...\n");

    my_print("my_print out ... \n");

    my_print("fd_tmp1 = %d,fd_tmp2 = %d\n",fd_tmp1,fd_tmp2);

    return 0;
}

在这里插入图片描述
这样既能用my_print输出到控制台,原来的printf也不会影响日志文件。

<think>我们参考上述引用,总结几种同时输出日志到控制台和文件的方法: 方法1:使用logging模块(官方推荐,适合记录不同级别的日志) 方法2:重定向sys.stdout,自定义一个类同时输出到控制台和文件(如引用2、3所示) 方法3:自定义一个打印类,在打印的同时写入文件(如引用4所示) 根据用户需求,我们分别介绍这几种方法。 方法1:使用logging模块 这是Python标准库中的模块,可以设置日志级别,同时添加多个处理器(Handler),比如一个输出到控制台,一个输出到文件。 示例代码: ``` import logging # 创建logger对象 logger = logging.getLogger('mylogger') logger.setLevel(logging.DEBUG) # 设置日志级别 # 创建文件处理器,将日志写入文件 file_handler = logging.FileHandler('test.log') file_handler.setLevel(logging.DEBUG) # 创建控制台处理器,将日志输出到控制台 console_handler = logging.StreamHandler() console_handler.setLevel(logging.DEBUG) # 设置日志格式 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 将处理器添加到logger logger.addHandler(file_handler) logger.addHandler(console_handler) # 测试日志 logger.debug('debug message') logger.info('info message') logger.warning('warning message') logger.error('error message') logger.critical('critical message') ``` 方法2:重定向sys.stdout 这种方法可以捕获所有print语句的输出,同时输出到控制台和文件。但是注意,它只能捕获标准输出,不能捕获标准错误(除非也重定向stderr),而且不能区分日志级别。 示例代码(参考引用2和3): ``` import sys import datetime class Logger(object): def __init__(self, filename): self.terminal = sys.stdout self.log = open(filename, "a", encoding='utf-8') # 追加模式,且设置编码 def write(self, message): self.terminal.write(message) self.log.write(message) def flush(self): # 因为文件操作需要flush,这里我们两个都flush self.terminal.flush() self.log.flush() # 使用示例 if __name__ == "__main__": # 定义日志文件路径,可以按时间命名 time_now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") log_file = f"log_{time_now}.log" sys.stdout = Logger(log_file) # 重定向标准输出 # 测试 print("开始记录日志...") print("当前时间:", datetime.datetime.now()) print("这条信息会同时输出到控制台和文件") # 注意:如果需要恢复标准输出,可以保存原来的sys.stdout,然后重新赋值 # 或者使用完后再改回去 ``` 方法3:自定义打印类(类似引用4) 这种方法不重定向sys.stdout,而是自己定义一个类,在类中实现同时输出到控制台和文件。 示例代码: ``` class IOStream: def __init__(self, filename): self.f = open(filename, 'a', encoding='utf-8') def cprint(self, text): # 同时打印到控制台和写入文件 print(text) self.f.write(text + '\n') self.f.flush() # 确保及时写入 def close(self): self.f.close() # 使用示例 if __name__ == "__main__": log_file = "run.log" io = IOStream(log_file) io.cprint("这是一条日志信息") io.cprint("另一条日志") io.close() # 记得关闭文件 ``` 总结: 1. 如果需要记录不同级别的日志(如debug, info, error等),推荐使用logging模块。 2. 如果只是简单记录print的输出,可以选择重定向sys.stdout或自定义打印类。 根据用户需求,选择合适的方法即可。 相问题: 1. 如何按日志级别过滤输出到控制台和文件? 2. 如何让日志文件按日期或大小自动分割? 3. 在重定向sys.stdout后,如何恢复标准输出? 4. 使用logging模块时,如何避免重复的日志记录?</think>### 如何在 Python 中将日志同时输出到控制台和文件 以下是三种常用方法,可根据需求选择: #### 方法 1:使用标准 `logging` 模块(推荐) ```python import logging # 创建日志记录器 logger = logging.getLogger("dual_logger") logger.setLevel(logging.DEBUG) # 设置日志级别 # 创建文件处理器 file_handler = logging.FileHandler("app.log", encoding="utf-8") file_handler.setLevel(logging.DEBUG) # 创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 控制台只显示INFO及以上级别 # 设置日志格式 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 添加处理器到记录器 logger.addHandler(file_handler) logger.addHandler(console_handler) # 使用示例 logger.debug("调试信息(仅文件可见)") logger.info("常规信息(文件和控制台都可见)") logger.warning("警告信息") ``` **特点**: - 官方推荐的标准方法[^1] - 可对不同处理器设置不同日志级别 - 支持日志轮转等高级功能 #### 方法 2:重定向 `sys.stdout`(捕获所有print输出) ```python import sys import datetime class DualLogger: def __init__(self, filename): self.terminal = sys.stdout self.log_file = open(filename, "a", encoding="utf-8") def write(self, message): self.terminal.write(message) self.log_file.write(message) def flush(self): self.terminal.flush() self.log_file.flush() # 使用示例 sys.stdout = DualLogger(f"log_{datetime.datetime.now().strftime('%Y%m%d')}.log") print("此信息同时输出到控制台和文件") print(42 * 3.14) ``` **特点**: - 自动捕获所有 `print()` 输出[^2][^3] - 无需修改现有代码中的 `print` 语句 - 简单直接,适合快速实现 #### 方法 3:自定义日志类(灵活控制) ```python class LogManager: def __init__(self, file_path): self.log_file = open(file_path, "a", encoding="utf-8") def log(self, message): # 输出到控制台 print(message) # 写入文件 self.log_file.write(f"{message}\n") self.log_file.flush() # 确保立即写入 def close(self): self.log_file.close() # 使用示例 logger = LogManager("application.log") logger.log("用户登录成功") logger.log(f"当前时间: {datetime.datetime.now()}") logger.close() ``` **特点**: - 完全自定义日志行为[^4] - 可添加时间戳、日志级别等额外信息 - 适合需要特殊格式的场景 ### 选择建议 1. 需要完整日志功能(分级、过滤等) → **方法1(logging模块)** 2. 只需捕获现有 `print` 语句 → **方法2(重定向stdout)** 3. 需要完全自定义日志格式 → **方法3(自定义类)** > **重要提示**:长期运行的应用程序应添加日志轮转机制,避免日志文件过大。对于方法1,可使用 `RotatingFileHandler` 或 `TimedRotatingFileHandler` 实现自动分割[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值