目录
使用场景
- 需要保存日志到本地,以便在程序异常时Debug
- 使用供应商的代码库,无法另存其日志
示例代码
新建一个文件命名为redirect_log.cpp,并将以下代码拷入文件。
cpp
#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
// 跨平台头文件
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#else
#include <unistd.h>
#include <fcntl.h>
#endif
// 全局变量:保存原始标准输出/标准错误的文件描述符
static int OriginalStdout = -1;
static int OriginalStderr = -1;
static FILE* LogFilePtr = nullptr;
/**
* @brief 开启日志重定向(控制台 + 文件双输出,无缓冲模式)
* @param logPath 日志文件路径
* @return 成功返回 true,失败返回 false
*/
static bool StartLogRedirect(const std::string& logPath) {
// 如果日志文件已打开,先关闭
if (LogFilePtr != nullptr) {
fclose(LogFilePtr);
LogFilePtr = nullptr;
}
// 直接用 C 文件接口打开,彻底避免 C++ fstream 兼容性问题
LogFilePtr = fopen(logPath.c_str(), "a+");
if (LogFilePtr == nullptr) {
std::cerr << "[Error] Failed to open log file: " << logPath << std::endl;
return false;
}
// ====================== 核心:关闭所有缓冲区 ======================
// 关闭 C++ 流的缓冲区
std::cout.rdbuf()->pubsetbuf(nullptr, 0);
std::cerr.rdbuf()->pubsetbuf(nullptr, 0);
// 关闭 C 标准IO的缓冲区,确保无换行也能立即输出
setvbuf(stdout, nullptr, _IONBF, 0);
setvbuf(stderr, nullptr, _IONBF, 0);
setvbuf(LogFilePtr, nullptr, _IONBF, 0);
// =================================================================
// 保存原始 stdout/stderr 文件描述符
#ifdef _WIN32
OriginalStdout = _dup(_fileno(stdout));
OriginalStderr = _dup(_fileno(stderr));
#else
OriginalStdout = dup(fileno(stdout));
OriginalStderr = dup(fileno(stderr));
#endif
if (OriginalStdout == -1 || OriginalStderr == -1) {
std::cerr << "[Error] Failed to save original file descriptors" << std::endl;
return false;
}
// 将标准输出/标准错误重定向到日志文件
#ifdef _WIN32
_dup2(_fileno(LogFilePtr), _fileno(stdout));
_dup2(_fileno(LogFilePtr), _fileno(stderr));
#else
dup2(fileno(LogFilePtr), STDOUT_FILENO);
dup2(fileno(LogFilePtr), STDERR_FILENO);
#endif
// 同步 C/C++ 流
std::ios::sync_with_stdio(true);
std::cout.clear();
std::cerr.clear();
std::cout << "========================================" << std::endl;
std::cout << "Log redirection started, output file: " << logPath << std::endl;
std::cout << "========================================" << std::endl;
return true;
}
/**
* @brief 停止日志重定向,恢复控制台原始输出
*/
static void StopLogRedirect() {
if (LogFilePtr == nullptr) {
return;
}
// 恢复原始的标准输出/标准错误
#ifdef _WIN32
_dup2(OriginalStdout, _fileno(stdout));
_dup2(OriginalStderr, _fileno(stderr));
_close(OriginalStdout);
_close(OriginalStderr);
#else
dup2(OriginalStdout, STDOUT_FILENO);
dup2(OriginalStderr, STDERR_FILENO);
close(OriginalStdout);
close(OriginalStderr);
#endif
// 关闭日志文件
fclose(LogFilePtr);
LogFilePtr = nullptr;
std::cout << "\n========================================" << std::endl;
std::cout << "Log redirection stopped, console output restored" << std::endl;
std::cout << "========================================" << std::endl;
}
// 测试主函数
int main() {
// 开启重定向
StartLogRedirect("runtime.log");
// 测试:没有换行也能立即输出
std::cout << "[cout] No newline message --> ";
std::cerr << "[cerr] No newline message --> ";
printf("[printf] No newline message --> ");
// 连续输出,实时写入文件
std::cout << "Test message 1 ";
std::cout << "Test message 2 ";
std::cerr << "Error message ";
// 循环输出测试
for (int i = 0; i < 3; ++i) {
std::cout << "\nLoop count: " << i;
}
// 停止重定向,恢复控制台
StopLogRedirect();
// 仅输出到控制台
std::cout << "\n[After Restore] This message only shows on console" << std::endl;
return 0;
}
编译代码
执行以下命令编译代码
powershell
g++ redirect_log.cpp -o log.exe -std=c++11
功能测试
使用以下命令执行应用程序
powershell
$ ./log.exe
控制台输出如下
html
========================================
Log redirection stopped, console output restored
========================================
[After Restore] This message only shows on console
日志文件runtime.log输出如下
html
========================================
Log redirection started, output file: runtime.log
========================================
[cout] No newline message --> [cerr] No newline message --> [printf] No newline message --> Test message 1 Test message 2 Error message
Loop count: 0
Loop count: 1
Loop count: 2