项目基本的使用在源码库的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,不会输出到文件
一个简单的例子:
#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为真时输出日志
LOG_IF(INFO, condition) << "info if message" 指定次数间隔输出
不带IF的是间隔10次输出一次,带IF的是条件成立,间隔10次输出一次
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次日志
LOG_FIRST_N(INFO, 20) << "Got the " << google::COUNTER << "th cookie";指定时间间隔输出
按照2.35s输出一次
LOG_EVERY_T(INFO, 2.35) << "Got a cookie";DLOG是LOG的一种变体,所有日志都会在Debug模式下输出,整体操作和LOG一样。
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参数来控制
#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;
}上面的输出结果
# ./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=0VLOG也支持LOG类似的其他操作(只支持条件输出, 间隔输出)
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。
#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, 当然也可以在设置这些参数, 比如:
#include <glog/logging.h>
int main(int argc, char* argv[]) {
FLAGS_colorlogtostderr = false;
FLAGS_minloglevel = 1;
}| 参数名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| FLAGS_timestamp_in_logfile_name | bool | true | 设置文件名是不是带时间戳 |
| FLAGS_logtostdout FLAGS_logtostderr | bool | false | 设置是否输出到stdout/stderr |
| FLAGS_alsologtostderr | bool | false | 设置除了文件输出外,还会输出到stderr |
| FLAGS_colorlogtostdout FLAGS_colorlogtostderr | bool | false | 设置输出到stdout/stderr的日志颜色,可能某些终端不支持 |
| FLAGS_stderrthreshold | int32 | ERROR(2) | 级别大于这个int值(INFO = 0…)的日志除了输出到文件,还会输出到标准错误 |
| FLAGS_log_file_header | bool | true | 在创建文件时写入日志的文件头(文件头内容为: Log file created at …) |
| FLAGS_log_prefix | bool | true | 在每一行输出前是否加上log前缀() |
| FLAGS_log_year_in_prefix | bool | true | 输出前缀中是否要年份 |
| FLAGS_logbuflevel | int32 | 0 | 级别比大于这个int值的日志会被缓存,其他会立即刷新 |
| FLAGS_logbufsecs | int32 | 30 | 设置日志在可以在缓冲的最大时间(单位:s),没有特殊情况超过该事件会刷新 |
| FLAGS_minloglevel | int32 | INFO(0) | 最低日志级别,高于这个级别的日志才会显示 |
| FLAGS_log_dir | string | 指定的日志目录 win:先使用GetTempPathA查询临时文件( C:\Users\username\AppData\Local\Temp\) 如果为空,会依次回退到或C:\TMP\或C:\TEMP\linux: 通过环境变量( TMPDIR或者TMP)拿到临时目录,如果为空,回退到/tmp/目录 | |
| FLAGS_logfile_mode | int32 | 0664 | 设置文件的打开模式 |
| FLAGS_log_link | string | "" | 附加的链接路径,glog会把当前正在生成的文件建立一个链接,这个链接每次打开都是最新生成的一次文件 |
| FLAGS_v | int32 | 0 | 设置自定义日志的打印级别(VLOG(INFO) << ....) |
| FLAGS_vmodule | string | "" | 设置每个模块的自定义日志的打印级别(<module name> = <log level>),module name是文件名称,log level是输出级别 |
| FLAGS_max_log_size | uint32 | 1800MB | 设置最大的文件大小(单位:MB) |
| FLAGS_stop_logging_if_full_disk | bool | false | 设置磁盘满时不要继续写磁盘 |
| FLAGS_log_utc_time | bool | false | 设置在日志中使用utc时间 |
既想输出到文件也想输出stderr流,
- 方法1,设置
FLAGS_stderrthreshold,当前日志级别>=该变量时,会输出到stderr - 方法2,设置
FLAGS_alsologtostderr,所有输出到文件的日志,都会输出到stderr
设计和实现
关键源码分析
LogMessage类
LogMessage是一个核心类的,基本日志LOG的宏展开后也是调用LogMessage这个类生成日志
// 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的基本接口
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函数初始化
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的初始化和其他一些初始化操作
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_方法,将日志内容写入日志文件
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
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的成员,报错了一条日志的所有配置信息和日志内容,下面该类的成员
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(),下面是这个方法的定义
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
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);
}- 如果是关键日志,最好使用
ERROR级别记录,因为INFO级别日志如果是程序崩溃之前的最后一条日志,很有可能不会打印 - glog保证多线程日志记录的顺序性是通过加锁实现,所以IO的时候会阻塞当前线程
- glog日志的多线程输出是无法保证完全的按照日志时间戳顺序的,不同线程的日志可能会出现
<0.1微秒级别的乱序