C++如何禁用自定义日志宏的所有stringstream输出
问题描述
我有一套日志模块,使用方式如下:
LOG(INFO) << "string";
该日志模块的定义如下:
#define LOG(status) LOG_##status.stream() #define LOG_ERROR LOG_INFO #define LOG_INFO LogMessage(__FILE__, __FUNCTION__, __LINE__, "I") #define LOG_DEBUG LogMessage(__FILE__, __FUNCTION__, __LINE__, "D") #define LOG_WARNING LogMessage(__FILE__, __FUNCTION__, __LINE__, "W") #define LOG_FATAL LogMessageFatal(__FILE__, __FUNCTION__, __LINE__) #define VLOG(level) \ VLogMessage(__FILE__, __FUNCTION__, __LINE__, level).stream() #define CHECK(x) \ if (!(x)) \ LogMessageFatal(__FILE__, __FUNCTION__, __LINE__).stream() \ << "Check failed: " #x << ": " // NOLINT(*) #define CHECK_EQ(x, y) _CHECK_BINARY(x, ==, y) #define CHECK_NE(x, y) _CHECK_BINARY(x, !=, y) #define CHECK_LT(x, y) _CHECK_BINARY(x, <, y) #define CHECK_LE(x, y) _CHECK_BINARY(x, <=, y) #define CHECK_GT(x, y) _CHECK_BINARY(x, >, y) #define CHECK_GE(x, y) _CHECK_BINARY(x, >=, y) #define _CHECK_BINARY(x, cmp, y) CHECK(x cmp y) << x << "!" #cmp << y << " " using namespace std; inline void gen_log(std::ostream &log_stream_, const char *file, const char *func, int lineno, const char *level, const int kMaxLen = 20) { const int len = strlen(file); std::string time_str; struct tm tm_time; // Time of creation of LogMessage time_t timestamp = time(NULL); localtime_r(×tamp, &tm_time); struct timeval tv; gettimeofday(&tv, NULL); log_stream_ << level << ' ' << 1 + tm_time.tm_mon << '/' << tm_time.tm_mday << ' ' << tm_time.tm_hour << ':' << tm_time.tm_min << ':' << std::setw(2) << tm_time.tm_sec << '.' << std::setw(3) << tv.tv_usec / 1000 << " "; if (len > kMaxLen) { log_stream_ << "..." << file + len - kMaxLen << " " << func << ":" << lineno << "] "; } else { log_stream_ << file << " " << func << ":" << lineno << "] "; } } class LogMessage { public: LogMessage(const char *file, const char *func, int lineno, const char *level = "I") { if (*level == 'I') { #if LOG_LEVEL <= 1 gen_log(log_stream_, file, func, lineno, level); #else log_stream_ = std::stringstream(nullptr); #endif } else if (*level == 'D') { #if LOG_LEVEL <= 2 gen_log(log_stream_, file, func, lineno, level); #else log_stream_ = std::stringstream(nullptr); #endif } else if (*level == 'W') { #if LOG_LEVEL <= 3 gen_log(log_stream_, file, func, lineno, level); #else log_stream_ = std::stringstream(nullptr); #endif } } ~LogMessage() { log_stream_ << '\n'; fprintf(stderr, "%s", log_stream_.str().c_str()); } std::ostream &stream() { return log_stream_; } protected: std::stringstream log_stream_; LogMessage(const LogMessage &) = delete; void operator=(const LogMessage &) = delete; };
现在我希望控制所有级别的LOG日志全部不打印输出,应该如何实现?
我个人的思路是返回空流,但不知道如何让<<运算符后拼接的日志内容被直接忽略,求可行的实现方案。
实现方案
你现有代码里用std::stringstream(nullptr)的写法存在缺陷:该构造会将流置为fail状态,虽然不会接收后续写入内容,但临时对象析构时仍会执行fprintf调用输出空串,部分编译环境下还会触发未定义行为。要实现全级别日志静默,有两种可靠方案,都能做到后续<<拼接的内容被完全忽略,无多余性能损耗。
方案1:编译期全关闭(零运行时开销,推荐)
利用现有LOG_LEVEL宏逻辑,做两处小修改即可:
- 编译时增加宏定义
-DLOG_LEVEL=4,或者在日志头文件最开头定义#define LOG_LEVEL 4,让所有INFO/DEBUG/WARNING级别日志都走禁用分支。 - 修改
LogMessage析构函数,增加流状态判断,禁用状态下直接跳过输出:
~LogMessage() { // 流处于fail状态说明当前级别被禁用,直接返回不输出 if (log_stream_.fail()) return; log_stream_ << '\n'; fprintf(stderr, "%s", log_stream_.str().c_str()); }
这个方案的原理是:当流被绑定到nullptr进入fail状态后,后续所有<<操作都会被C++标准库的流逻辑直接短路,不会执行任何参数序列化、字符串拼接操作,析构时跳过IO调用,全程无多余开销。
注意:LOG_FATAL和CHECK系列宏依赖LogMessageFatal类,如果需要完全关闭所有输出(包括致命错误日志),给LogMessageFatal也加上相同的级别判断和析构跳过逻辑即可。
方案2:实现专用空流类(支持运行时动态开关)
如果需要运行时灵活控制日志开关,可以实现一个吞掉所有输入的空流类,重载<<运算符直接返回自身,不做任何处理:
// 空流类,所有写入操作直接忽略 class NullStream { public: template<typename T> NullStream& operator<<(const T&) { return *this; } // 重载兼容endl、setw等标准流操纵符 NullStream& operator<<(std::ostream& (*)(std::ostream&)) { return *this; } };
之后修改LogMessage类和LOG宏,禁用状态下返回空流实例,跳过输出逻辑:
class LogMessage { public: LogMessage(const char *file, const char *func, int lineno, const char *level = "I") : enabled_(false) { // 这里可以替换为任意判断逻辑,支持编译期常量、运行时全局开关等 bool should_log = false; // 全关场景直接设为false即可 if (should_log) { enabled_ = true; gen_log(log_stream_, file, func, lineno, level); } } ~LogMessage() { if (!enabled_) return; log_stream_ << '\n'; fprintf(stderr, "%s", log_stream_.str().c_str()); } std::ostream& stream() { return log_stream_; } NullStream& null_stream() { return null_stream_; } protected: bool enabled_; std::stringstream log_stream_; NullStream null_stream_; LogMessage(const LogMessage &) = delete; void operator=(const LogMessage &) = delete; }; // 修改LOG宏,禁用时返回空流 #define LOG(status) (LOG_##status.enabled_ ? LOG_##status.stream() : LOG_##status.null_stream())
这个方案下,无论<<后拼接什么类型的参数,都会被空流直接丢弃,不会产生任何字符串构造、序列化开销,开关状态可以在运行时动态调整。
内容的提问来源于stack exchange,提问作者Nicholas Jela
相关产品推荐
相关产品推荐

