You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(&timestamp, &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宏逻辑,做两处小修改即可:

  1. 编译时增加宏定义-DLOG_LEVEL=4,或者在日志头文件最开头定义#define LOG_LEVEL 4,让所有INFO/DEBUG/WARNING级别日志都走禁用分支。
  2. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 05:15:30