项目基本的使用在源码库的readme里比较详细,我这里只是摘出了部分讨论

glog是一个高性能、轻量级的同步IO日志记录库,同时是一个同步的、多线程安全的日志库。它没有使用一个独立线程来刷新和保存日志,而是直接在调用线程写日志,glog通过local_thread变量,日志缓存等功能来提高性能

基本使用

LOG

在日志系统中一般有多种日志级别,分别如下

TRACE: 来跟踪函数的调用,提示函数的调用关系,一般只在DEBUG模式下使用

DEBUG: 用来在开发过程中打印一些运行信息,一般也是只在DEBUG模式下使用

INFO: 打印感兴趣或者重要的信息,这个日志开始,后面的日志一般在RELEASE模式(生产环境)中使用

WARN: 表示出现了潜在的错误,打印一些提示信息,比如缓冲区快满了,CPU使用超过80%了,没找到参数,使用了默认参数等…

ERROR: 表示发生了错误,但是不影响系统的继续运行,一般在WARN之后的级别打印错误时,同时会打印错误码

FATAL:表示该错误严重到会导致程序的退出,程序无法自我恢复,必须通过重启解决

基本输出

glog是日志的最简单的使用方式,支持四个日志级别INFO,WARNING, ERROR,FATAL

⚠️ 警告

FATAL会导致代码崩溃,调用FATAL级别日志会在内部调用abort()函数

如果不设置google::InitGoogleLogging(argv[0]);,日志会只输出到stderr,不会输出到文件

一个简单的例子:

C++
#include <glog/logging.h>

int main(int argc, char* argv[]) {
  	// Initialize Google’s logging library.
  	google::InitGoogleLogging(argv[0]);
    
  	LOG(INFO) << "info message";
  	LOG(WARNING) << "warning message";
  	LOG(ERROR) << "error message";
    // LOG(FATAL) << "fatal message";
  	
    return 0;
}

/** outout
* I20240129 11:22:38.030303     1 Logging_test.cpp:16] info message
* W20240129 11:22:38.030426     1 Logging_test.cpp:17] warning message
* E20240129 11:22:38.030429     1 Logging_test.cpp:18] error message
*/

条件输出

条件condition为真时输出日志

C++
LOG_IF(INFO, condition) << "info if message" 

指定次数间隔输出

不带IF的是间隔10次输出一次,带IF的是条件成立,间隔10次输出一次

C++
LOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookie";
LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << google::COUNTER << "th big cookie";

只输出前几次的日志

输出前20次日志

C++
LOG_FIRST_N(INFO, 20) << "Got the " << google::COUNTER << "th cookie";

指定时间间隔输出

按照2.35s输出一次

C++
LOG_EVERY_T(INFO, 2.35) << "Got a cookie";

DLOGLOG的一种变体,所有日志都会在Debug模式下输出,整体操作和LOG一样。

C++
DLOG(INFO) << "Found cookies";
DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
DLOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookie";
DLOG_FIRST_N(INFO, 10) << "Got the " << google::COUNTER << "th cookie";
DLOG_EVERY_T(INFO, 0.01) << "Got a cookie";

VLOG

这是glog提供的用户自定义分级信息,该分级和LOG的级别是独立的,可以在命令行通过--v--vmodule参数来控制

C++
#include <gflags/gflags.h>
#include <glog/logging.h>

int main(int argc, char* argv[]) {
  	google::ParseCommandLineFlags(&argc, &argv, true);
  	google::InitGoogleLogging(argv[0]);
   
  	// FLAGS_v = 25;  // 日志级别小于25的都会打印
    VLOG(10) << "vlog 10";
    VLOG(20) << "vlog 20";
    VLOG(30) << "vlog 30";
    VLOG(40) << "vlog 40";

    if(VLOG_IS_ON(20)){  // 日志级别(FLAGS_v)大于20时条件成立
        VLOG(10) << "other vlog 10";
        VLOG(20) << "other vlog 20";
        VLOG(30) << "other vlog 20";
        VLOG(40) << "other vlog 20";
    }
    
  	return 0;
}

上面的输出结果

Bash
# ./app --logtostderr=1 -v=15
I20240129 13:29:57.710283 18618 main.cpp:9] vlog 10

# ./Logging_test --logtostderr=1 -v=25
I20240129 13:29:19.255123 18304 main.cpp:9] vlog 10
I20240129 13:29:19.255245 18304 main.cpp:10] vlog 20
I20240129 13:29:19.255271 18304 main.cpp:15] other vlog 10
I20240129 13:29:19.255283 18304 main.cpp:16] other vlog 20

# ./app --logtostderr=1 -v=35
I20240129 13:30:20.680181 18807 main.cpp:9] vlog 10
I20240129 13:30:20.680296 18807 main.cpp:10] vlog 20
I20240129 13:30:20.680306 18807 main.cpp:11] vlog 30
I20240129 13:30:20.680330 18807 main.cpp:15] other vlog 10
I20240129 13:30:20.680336 18807 main.cpp:16] other vlog 20
I20240129 13:30:20.680349 18807 main.cpp:17] other vlog 20

# 设置了--vmodule=main=10 表示在main模块(文件)的日志只按照10处理
# ./app --logtostderr=1 -v=35 --vmodule=main=10
I20240129 13:31:27.487478 19333 main.cpp:9] vlog 10

# 当然 --vmodule是支持通配符的比如 gfs*表示以gfs为前缀的文件的消息
# ./app --vmodule=mapreduce=2,file=1,gfs*=3 --v=0

VLOG也支持LOG类似的其他操作(只支持条件输出, 间隔输出)

C++
VLOG_IF(10, num_cookies > 10) << "Got lots of cookies";
VLOG_EVERY_N(10, 10) << "Got the " << google::COUNTER << "th cookie";
VLOG_IF_EVERY_N(10, num_cookies > 10, 10) << "Got the " << google::COUNTER << "th cookie";

RAW_LOG

RAW_LOG是比LOG,VLOG==更轻量的原始日志打印方式==,该日志打印不分配线程或者锁,直接记录到stderr,没有缓冲,会截断非常长的消息字符串。

RAW_LOG主要用来日志系统还没有被初始化(设置输出文件,输出级别…)时候,输出一些内容到stderr

C++
#include <glog/logging.h>
#include <glog/raw_logging.h>

int main(int argc, char* argv[]) {
	RAW_LOG(INFO,
            "Test: v=%d stderrthreshold=%d logtostderr=%d alsologtostderr=%d",
            FLAGS_v,
            FLAGS_stderrthreshold,
            FLAGS_logtostderr,
            FLAGS_alsologtostderr);
}

/** output
* I00000000 00:00:00.000000     1 main.cpp:5] RAW: Test: v=0 stderrthreshold=2 logtostderr=0 alsologtostderr=0
/

CHECK断言

CHECK宏是条件成立断言成功,操作和assert一样,但是assert只会在Debug模式下使用(NDEBUG宏控制),但是glog的断言可在Debug和Release模式下使用

CHECK 宏行为
CHECK(condition)检查 condition 为真
CHECK_EQ(val1, val2)检查 val1 == val2
CHECK_NE(val1, val2)检查 val1 != val2
CHECK_LE(val1, val2)检查 val1 < val2
CHECK_LT(val1, val2)检查 val1 <= val2
CHECK_GE(val1, val2)检查 val1 > val2
CHECK_GT(val1, val2)检查 val1 >= val2
CHECK_NOTNULL(val)检查 val 为 true
CHECK_STREQ(s1, s2)检查字符串 s1 == s2
CHECK_STRNE(s1, s2)检查字符串 s1 != s2
CHECK_STRCASEEQ(s1, s2)检查字符串 s1 == s2(忽略大小写)
CHECK_STRCASENE(s1, s2)检查字符串 s1 != s2(忽略大小写)
CHECK_INDEX(I,A)CHECK(I < (sizeof(A)/sizeof(A[0])))
CHECK_BOUND(B,A)CHECK(B <= (sizeof(A)/sizeof(A[0])))
CHECK_DOUBLE_EQ(val1, val2)检查浮点数 val1 == val2
CHECK_NEAR(val1, val2, margin)检查浮点数 |val1 - val2| <= margin

全局可配置参数

glog的支持一些全局配置参数,用来配置glog程序,比较重要的几个参数我用特殊颜色标注了

如果当前程序也使用了gflags可以使用命令行解析传入这些参数,参数名称是下面这个参数去掉FLAGS_,可以这样启动程序./your_app --logtostderr=1

如果当前程序使用没有使用gflags,可以这样启动程序GLOG_logtostderr=1 ./your_app, 当然也可以在设置这些参数, 比如:

C++
#include <glog/logging.h>

int main(int argc, char* argv[]) {
	FLAGS_colorlogtostderr = false;
    FLAGS_minloglevel = 1;
}
参数名称类型默认值说明
FLAGS_timestamp_in_logfile_namebooltrue设置文件名是不是带时间戳
FLAGS_logtostdout
FLAGS_logtostderr
boolfalse设置是否输出到stdout/stderr
FLAGS_alsologtostderrboolfalse设置除了文件输出外,还会输出到stderr
FLAGS_colorlogtostdout
FLAGS_colorlogtostderr
boolfalse设置输出到stdout/stderr的日志颜色,可能某些终端不支持
FLAGS_stderrthresholdint32ERROR(2)级别大于这个int值(INFO = 0…)的日志除了输出到文件,还会输出到标准错误
FLAGS_log_file_headerbooltrue在创建文件时写入日志的文件头(文件头内容为: Log file created at …)
FLAGS_log_prefixbooltrue在每一行输出前是否加上log前缀()
FLAGS_log_year_in_prefixbooltrue输出前缀中是否要年份
FLAGS_logbuflevelint320级别比大于这个int值的日志会被缓存,其他会立即刷新
FLAGS_logbufsecsint3230设置日志在可以在缓冲的最大时间(单位:s),没有特殊情况超过该事件会刷新
FLAGS_minloglevelint32INFO(0)最低日志级别,高于这个级别的日志才会显示
FLAGS_log_dirstring指定的日志目录
win:先使用GetTempPathA查询临时文件(C:\Users\username\AppData\Local\Temp\) 如果为空,会依次回退到或C:\TMP\C:\TEMP\
linux: 通过环境变量(TMPDIR或者TMP)拿到临时目录,如果为空,回退到/tmp/目录
FLAGS_logfile_modeint320664设置文件的打开模式
FLAGS_log_linkstring""附加的链接路径,glog会把当前正在生成的文件建立一个链接,这个链接每次打开都是最新生成的一次文件
FLAGS_vint320设置自定义日志的打印级别(VLOG(INFO) << ....)
FLAGS_vmodulestring""设置每个模块的自定义日志的打印级别(<module name> = <log level>),module name是文件名称,log level是输出级别
FLAGS_max_log_sizeuint321800MB设置最大的文件大小(单位:MB)
FLAGS_stop_logging_if_full_diskboolfalse设置磁盘满时不要继续写磁盘
FLAGS_log_utc_timeboolfalse设置在日志中使用utc时间
💡 提示

既想输出到文件也想输出stderr流,

  • 方法1,设置FLAGS_stderrthreshold,当前日志级别>=该变量时,会输出到stderr
  • 方法2,设置FLAGS_alsologtostderr,所有输出到文件的日志,都会输出到stderr

设计和实现

关键源码分析

LogMessage类

LogMessage是一个核心类的,基本日志LOG的宏展开后也是调用LogMessage这个类生成日志

C++
// severity是日志的等级(INFO,WARNING,ERROR, FATAL)
#define LOG(severity) COMPACT_GOOGLE_LOG_##severity.stream()

// LogMessageFatal是继承LogMessage的
#define COMPACT_GOOGLE_LOG_INFO google::LogMessage(__FILE__, __LINE__)
#define COMPACT_GOOGLE_LOG_WARNING google::LogMessage(__FILE__, __LINE__, google::GLOG_WARNING)
#define COMPACT_GOOGLE_LOG_ERROR google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR)
#define COMPACT_GOOGLE_LOG_FATAL google::LogMessageFatal(__FILE__, __LINE__)

// TEST
LOG(INFO) << "this is info log";
// 上面这条日志展开之后如下, 日志会在析构时分发到指定位置(文件或stderr)
google::LogMessage(__FILE__,__LINE__).stream() << "this is info log";

LogMessage的基本接口

C++
class LogMessage {
 public:
  enum {
    // 控制日志消息前缀
    kNoLogPrefix = -1
  };

  // 日志输出的嵌套类
  class LogStream : public std::ostream {...};

 public:
  // 回调函数指针,设置具体的发送方法,比如发送到文件,发送到stderr
  typedef void (LogMessage::*SendMethod)();

  // file:文件名,line:日志行,severity:日志等级,ctr:计数器(LOG_EVERY_X()会用到该参数), send_method:发送方法
  LogMessage(const char* file, int line, LogSeverity severity, int64 ctr, SendMethod send_method);

  // 两参数构造 severity = INFO, ctr = 0, send_method = &LogMessage::SendToLog.
  LogMessage(const char* file, int line);

  // ctr = 0, send_method = &LogMessage::SendToLog
  LogMessage(const char* file, int line, LogSeverity severity);
  
  // sink 是一个自定义的日志内容写入方法,可以使用AddLogSink()添加全局Sink,或者
  // ctr = 0, send_method = &LogMessage::SendToSinkAndLog if also_send_to_log is true,send_method = &LogMessage::SendToSink
  LogMessage(const char* file, int line, LogSeverity severity, LogSink* sink, bool also_send_to_log);

  // outvec是sink是同级别的可选项,ctr = 0, send_method = &LogMessage::SaveOrSendToLog.
  LogMessage(const char* file, int line, LogSeverity severity, std::vector<std::string>* outvec);

  // message是sink是同级别的可选项,ctr = 0, send_method = &LogMessage::WriteToStringAndLog.
  LogMessage(const char* file, int line, LogSeverity severity, std::string* message);

  // A special constructor used for check failures
  LogMessage(const char* file, int line, const logging::internal::CheckOpString& result);

  ~LogMessage();

  // 将缓冲消息刷新到构造函数中设置的sink,一般是在析构函数中调用,也可以从其他地方调用,只要第一次被调用后,后面的调用会被忽略
  void Flush();

  // 单个日志消息的长度限制,默认值为30000
  static const size_t kMaxLogMessageLen;

  // 默认的两个日志分发位置
  void SendToLog();           // Actually dispatch to the logs
  void SendToSyslogAndLog();  // Actually dispatch to syslog and the logs

  // 调用abort()或者执行LOG(FATAL)方法的调用
  [[noreturn]] static void Fail();

  std::ostream& stream();

  int preserved_errno() const;

  static int64 num_messages(int severity);

  LogSeverity severity() const noexcept;
  int line() const noexcept;
  const std::thread::id& thread_id() const noexcept;
  const char* fullname() const noexcept;
  const char* basename() const noexcept;
  const LogMessageTime& time() const noexcept;

  LogMessage(const LogMessage&) = delete;
  LogMessage& operator=(const LogMessage&) = delete;

 private:
  // Fully internal SendMethod cases:
  void SendToSinkAndLog();  // Send to sink if provided and dispatch to the logs
  void SendToSink();        // Send to sink if provided, do nothing otherwise.

  // Write to string if provided and dispatch to the logs.
  void WriteToStringAndLog();

  void SaveOrSendToLog();  // Save to stringvec if provided, else to logs

  // 构造函数内部都会调用该函数
  void Init(const char* file, int line, LogSeverity severity, void (LogMessage::*send_method)());

  // Used to fill in crash information during LOG(FATAL) failures.
  void RecordCrashReason(logging::internal::CrashReason* reason);

  // Counts of messages sent at each priority:
  static int64 num_messages_[NUM_SEVERITIES];  // under log_mutex

  // 日志消息会保存在该结构体中,大多数情况下data_会指向一个thread_local预分配的对齐的内存,
  // 当thread_msg_data被使用(还未完成本次刷新)时会从堆上申请内存,data_会指向堆上申请的内存allocated_(堆上分配的内存), 这个内存会在析构函数释放
  logging::internal::LogMessageData* allocated_;
  logging::internal::LogMessageData* data_;
  LogMessageTime time_;  // 获取日志时间
};

LogMessage的构造函数基本都是调用Init函数初始化

C++
LogMessage::LogMessage(const char* file, int line, LogSeverity severity,
                       int64 ctr, void (LogMessage::*send_method)())
    : allocated_(nullptr) {
  Init(file, line, severity, send_method);
  data_->stream_.set_ctr(ctr);
}

LogMessage::LogMessage(const char* file, int line) : allocated_(nullptr) {
  Init(file, line, GLOG_INFO, &LogMessage::SendToLog);
}

LogMessage::LogMessage(const char* file, int line, LogSeverity severity)
    : allocated_(nullptr) {
  Init(file, line, severity, &LogMessage::SendToLog);
}

LogMessage::LogMessage(const char* file, int line, LogSeverity severity,
                       LogSink* sink, bool also_send_to_log)
    : allocated_(nullptr) {
  Init(file, line, severity,
       also_send_to_log ? &LogMessage::SendToSinkAndLog
                        : &LogMessage::SendToSink);
  data_->sink_ = sink;  // override Init()'s setting to nullptr
}

LogMessage::LogMessage(const char* file, int line, LogSeverity severity,
                       vector<string>* outvec)
    : allocated_(nullptr) {
  Init(file, line, severity, &LogMessage::SaveOrSendToLog);
  data_->outvec_ = outvec;  // override Init()'s setting to nullptr
}

LogMessage::LogMessage(const char* file, int line, LogSeverity severity,
                       string* message)
    : allocated_(nullptr) {
  Init(file, line, severity, &LogMessage::WriteToStringAndLog);
  data_->message_ = message;  // override Init()'s setting to nullptr
}

LogMessage::LogMessage(const char* file, int line,
                       const logging::internal::CheckOpString& result)
    : allocated_(nullptr) {
  Init(file, line, GLOG_FATAL, &LogMessage::SendToLog);
  stream() << "Check failed: " << (*result.str_) << " ";
}

LogMessage::Init()主要完成LogMessageData的初始化和其他一些初始化操作

C++
void LogMessage::Init(const char* file, int line, LogSeverity severity,
                      void (LogMessage::*send_method)()) {
  allocated_ = nullptr;
  if (severity != GLOG_FATAL || !exit_on_dfatal) {
#ifdef GLOG_THREAD_LOCAL_STORAGE
    // No need for locking, because this is thread local.
    if (thread_data_available) { // 默认为true,会在LogMessage析构时设置为true
      thread_data_available = false;
      /*
      * thread_mess_data: 实际执行的下面的代码, 申请一个局部线程的对齐的内存地址
      * static thread_local std::aligned_storage<sizeof(logging::internal::LogMessageData),   
      * alignof(logging::internal::LogMessageData)>::type thread_msg_data;
      */
      data_ = new (&thread_msg_data) logging::internal::LogMessageData;
    } else {
      allocated_ = new logging::internal::LogMessageData();
      data_ = allocated_;
    }
#else   // !defined(GLOG_THREAD_LOCAL_STORAGE)
    allocated_ = new logging::internal::LogMessageData();
    data_ = allocated_;
#endif  // defined(GLOG_THREAD_LOCAL_STORAGE)
    data_->first_fatal_ = false;
  } else { // LOG(FATAL)时执行, 只生成一次,互斥使用
    std::lock_guard<std::mutex> l{fatal_msg_lock};
    if (fatal_msg_exclusive) {  // 全局变量 默认为true
      fatal_msg_exclusive = false;
      data_ = &fatal_msg_data_exclusive;  // 全局定义的一个LogMessageData静态对象
      data_->first_fatal_ = true;
    } else {
      data_ = &fatal_msg_data_shared;  // 全局定义的一个LogMessageData静态对象
      data_->first_fatal_ = false;
    }
  }

  data_->preserved_errno_ = errno;
  data_->severity_ = severity;
  data_->line_ = line;
  data_->send_method_ = send_method;
  data_->sink_ = nullptr;
  data_->outvec_ = nullptr;

  const auto now = std::chrono::system_clock::now();
  time_ = LogMessageTime(now);

  data_->num_chars_to_log_ = 0;
  data_->num_chars_to_syslog_ = 0;
  data_->basename_ = const_basename(file);
  data_->fullname_ = file;
  data_->has_been_flushed_ = false;
  data_->thread_id_ = std::this_thread::get_id();

  // If specified, prepend a prefix to each line.  For example:
  //    I20201018 160715 f5d4fbb0 logging.cc:1153]
  //    (log level, GMT year, month, date, time, thread_id, file basename, line)
  // We exclude the thread_id for the default thread.
  if (FLAGS_log_prefix && (line != kNoLogPrefix)) {   // 设置日志输出前缀, 默认格式
    std::ios saved_fmt(nullptr);
    saved_fmt.copyfmt(stream());
    stream().fill('0');
    if (g_prefix_formatter == nullptr) {
      stream() << LogSeverityNames[severity][0];
      if (FLAGS_log_year_in_prefix) {
        stream() << setw(4) << 1900 + time_.year();
      }
      stream() << setw(2) << 1 + time_.month() << setw(2) << time_.day() << ' '
               << setw(2) << time_.hour() << ':' << setw(2) << time_.min()
               << ':' << setw(2) << time_.sec() << "." << setw(6)
               << time_.usec() << ' ' << setfill(' ') << setw(5)
               << data_->thread_id_ << setfill('0') << ' ' << data_->basename_
               << ':' << data_->line_ << "] ";
    } else {
      (*g_prefix_formatter)(stream(), *this);
      stream() << " ";
    }
    stream().copyfmt(saved_fmt);
  }
  data_->num_prefix_chars_ = data_->stream_.pcount();

  if (!FLAGS_log_backtrace_at.empty()) {
    char fileline[128];
    std::snprintf(fileline, sizeof(fileline), "%s:%d", data_->basename_, line);
#ifdef HAVE_STACKTRACE
    if (FLAGS_log_backtrace_at == fileline) {
      string stacktrace;
      DumpStackTraceToString(&stacktrace);
      stream() << " (stacktrace:\n" << stacktrace << ") ";
    }
#endif
  }
}

Flush()函数会调用提前在构造函数设置的send_method_方法,将日志内容写入日志文件

C++
void LogMessage::Flush() {
  if (data_->has_been_flushed_ || data_->severity_ < FLAGS_minloglevel) {
    return;
  }

  data_->num_chars_to_log_ = data_->stream_.pcount();
  data_->num_chars_to_syslog_ = data_->num_chars_to_log_ - data_->num_prefix_chars_;

  // Do we need to add a \n to the end of this message?
  bool append_newline = (data_->message_text_[data_->num_chars_to_log_ - 1] != '\n');
  char original_final_char = '\0';

  // 日志最后一个字符不是换行符的的话,最后一行插入一个换行符
  if (append_newline) {
    original_final_char = data_->message_text_[data_->num_chars_to_log_];
    data_->message_text_[data_->num_chars_to_log_++] = '\n';
  }
  
  // 设置日志尾部为'\0'
  data_->message_text_[data_->num_chars_to_log_] = '\0';

  {
    std::lock_guard<std::mutex> l{log_mutex};
    (this->*(data_->send_method_))();  // 真正写入日志的地方, 调用 send_method_指向的方法
    ++num_messages_[static_cast<int>(data_->severity_)];  // 记录每个级别日志调用的次数
  }
  LogDestination::WaitForSinks(data_);  // 等候Sink写入完成

  if (append_newline) {
    data_->message_text_[data_->num_chars_to_log_ - 1] = original_final_char;  // 恢复之前的最后一个字符(去掉换行符)
  }

  if (data_->preserved_errno_ != 0) {
    errno = data_->preserved_errno_;
  }

  data_->has_been_flushed_ = true;
}
💡 提示

如果是FATAL级别的日志不会刷新,会直接开始落盘,并且让进程结束

LogMessage::~LogMessage()析构函数会调用Flush(), 将所有日志信息刷新到Sink

C++
LogMessage::~LogMessage() {
  Flush();
#ifdef GLOG_THREAD_LOCAL_STORAGE
  if (data_ == static_cast<void*>(&thread_msg_data)) {
    data_->~LogMessageData();
    thread_data_available = true;
  } else {
    delete allocated_;
  }
#else   // !defined(GLOG_THREAD_LOCAL_STORAGE)
  delete allocated_;
#endif  // defined(GLOG_THREAD_LOCAL_STORAGE)
}

LogMessageData

LogMessageData是LogMessage的成员,报错了一条日志的所有配置信息和日志内容,下面该类的成员

C++
struct LogMessageData {
  LogMessageData::LogMessageData()
    : stream_(message_text_, LogMessage::kMaxLogMessageLen, 0) {}

  int preserved_errno_;  // preserved errno
  // Buffer space; contains complete message text.
  char message_text_[LogMessage::kMaxLogMessageLen + 1];  // 这是实际一条日志内容的地方, 默认是30000字节
  LogMessage::LogStream stream_;  // 输出流 LogStream是一个继承了std::ostream的类,基本操作和ostream一直
  LogSeverity severity_;  // 日志级别
  int line_;              // 日志打印行号
  void (LogMessage::*send_method_)();  //  日志消息实际写入到文件/stderr的全局函数
  union {  // 这个联合体是用来放sink的指针,只能用一个
    LogSink* sink_;  // nullptr or sink to send message to
    std::vector<std::string>* outvec_;            // nullptr or vector to push message onto
    std::string* message_;  // nullptr or string to write message into
  };
  size_t num_prefix_chars_;     // 日志前缀长度
  size_t num_chars_to_log_;     // 所有日志长度
  size_t num_chars_to_syslog_;  // 日志内容(message)长度 = 所有日志长度 - 日志前缀长度
  const char* basename_;        // 这个basename_和下面的fullname_是文件的名称和带绝对路径的path
  const char* fullname_;        // 
  bool has_been_flushed_;       // 是否已经刷新
  bool first_fatal_;            // 第一次错误
  std::thread::id thread_id_;   // 线程ID

  LogMessageData(const LogMessageData&) = delete;
  LogMessageData& operator=(const LogMessageData&) = delete;
};

LogMessage的send_method_有一个默认值是SendToLog(),下面是这个方法的定义

C++
void LogMessage::SendToLog() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) {
  static bool already_warned_before_initgoogle = false;

  // 检查日志是否为空或者尾部不为'\n'
  RAW_DCHECK(data_->num_chars_to_log_ > 0 &&
                 data_->message_text_[data_->num_chars_to_log_ - 1] == '\n',
             "");
    
  // 如果有高于该严重级别的日志也会写入该级别日志中
  // 也就是说 INFO文件中包含WARNING,ERROR的日志
    
  // 如果之前没有做初始化操作,会默认给stderr输出一条警告提示信息
  if (!already_warned_before_initgoogle && !IsGoogleLoggingInitialized()) {
    const char w[] =
        "WARNING: Logging before InitGoogleLogging() is "
        "written to STDERR\n";
    WriteToStderr(w, strlen(w));
    already_warned_before_initgoogle = true;
  }

  // 如果没有解析到当前程序名(通过InitGoogleLogging(argv[0])), 则只会输出到stderr流
  // 设置了FLAGS_logtostderr 或者 FLAGS_logtostdout标志会输出到对应标准输出,不会输出到文件
  if (FLAGS_logtostderr || FLAGS_logtostdout || !IsGoogleLoggingInitialized()) {
    if (FLAGS_logtostdout) {
      ColoredWriteToStdout(data_->severity_, data_->message_text_,
                           data_->num_chars_to_log_);
    } else {
      ColoredWriteToStderr(data_->severity_, data_->message_text_,
                           data_->num_chars_to_log_);
    }

    // 如果用户传入了sink函数,则在该函数中调用,如果没有,则什么都不干
    LogDestination::LogToSinks(
        data_->severity_, data_->fullname_, data_->basename_, data_->line_,
        time_, data_->message_text_ + data_->num_prefix_chars_,
        (data_->num_chars_to_log_ - data_->num_prefix_chars_ - 1));
  } else {
    // log this message to all log files of severity <= severity_
    LogDestination::LogToAllLogfiles(data_->severity_, time_.when(),
                                     data_->message_text_,
                                     data_->num_chars_to_log_);
	// 如果设置了FLAGS_stderrthreshold, 当severity >= FLAGS_stderrthreshold 或者 FLAGS_alsologtostderr时候既输出到文件,也输出到stderr
    LogDestination::MaybeLogToStderr(data_->severity_, data_->message_text_,
                                     data_->num_chars_to_log_,
                                     data_->num_prefix_chars_);
    LogDestination::MaybeLogToEmail(data_->severity_, data_->message_text_,
                                    data_->num_chars_to_log_);
    LogDestination::LogToSinks(
        data_->severity_, data_->fullname_, data_->basename_, data_->line_,
        time_, data_->message_text_ + data_->num_prefix_chars_,
        (data_->num_chars_to_log_ - data_->num_prefix_chars_ - 1));
    // NOTE: -1 removes trailing \n
  }

  // 输出日志级别为 FATAL时调用
  if (data_->severity_ == GLOG_FATAL && exit_on_dfatal) {
    if (data_->first_fatal_) {
      // Store crash information so that it is accessible from within signal
      // handlers that may be invoked later.
      RecordCrashReason(&crash_reason);
      SetCrashReason(&crash_reason);

      // Store shortened fatal message for other logs and GWQ status
      const size_t copy =
          min(data_->num_chars_to_log_, sizeof(fatal_message) - 1);
      memcpy(fatal_message, data_->message_text_, copy);
      fatal_message[copy] = '\0';
      fatal_time = time_.when();
    }

    if (!FLAGS_logtostderr && !FLAGS_logtostdout) {
      for (auto& log_destination : LogDestination::log_destinations_) {
        if (log_destination) {
          log_destination->logger_->Write(
              true, std::chrono::system_clock::time_point{}, "", 0);
        }
      }
    }

    // release the lock that our caller (directly or indirectly)
    // LogMessage::~LogMessage() grabbed so that signal handlers
    // can use the logging facility. Alternately, we could add
    // an entire unsafe logging interface to bypass locking
    // for signal handlers but this seems simpler.
    log_mutex.unlock();
    LogDestination::WaitForSinks(data_);

    const char* message = "*** Check failure stack trace: ***\n";
    if (write(fileno(stderr), message, strlen(message)) < 0) {
      // Ignore errors.
    }
    AlsoErrorWrite(GLOG_FATAL,
                   glog_internal_namespace_::ProgramInvocationShortName(),
                   message);
    Fail();
  }
}

日志在落盘时会调用MayBeLogToLogfile

C++
inline void LogDestination::LogToAllLogfiles(
    LogSeverity severity,
    const std::chrono::system_clock::time_point& timestamp, const char* message,
    size_t len) {
  if (FLAGS_logtostdout) {  // global flag: never log to file
    ColoredWriteToStdout(severity, message, len);
  } else if (FLAGS_logtostderr) {  // global flag: never log to file
    ColoredWriteToStderr(severity, message, len);
  } else {
    // 日志写入,会写入三个文件,每个级别一个文件
    for (int i = severity; i >= 0; --i) {
      LogDestination::MaybeLogToLogfile(static_cast<LogSeverity>(i), timestamp,
                                        message, len);
    }
  }
}

inline void LogDestination::MaybeLogToLogfile(
    LogSeverity severity,
    const std::chrono::system_clock::time_point& timestamp, const char* message,
    size_t len) {
  // 判断是否需要立即刷新,FLAGS_logbuflevel默认值为0,默认只会对INFO级别缓存,其他级别都是立即刷新
  const bool should_flush = severity > FLAGS_logbuflevel;
  // 获取该级别对应的 LogDestination
  LogDestination* destination = log_destination(severity);
  destination->logger_->Write(should_flush, timestamp, message, len);
}
💡 提示
  1. 如果是关键日志,最好使用ERROR级别记录,因为INFO级别日志如果是程序崩溃之前的最后一条日志,很有可能不会打印
  2. glog保证多线程日志记录的顺序性是通过加锁实现,所以IO的时候会阻塞当前线程
  3. glog日志的多线程输出是无法保证完全的按照日志时间戳顺序的,不同线程的日志可能会出现<0.1微秒级别的乱序