C++项目中常用的日志库有glog和spdlog
之前看了一下glog的源码,一些内容记录分享一下。
glog是谷歌开源的一个日志库,使用 c++98 编写
注意:
https://github.com/google/glog
glog支持bazel和cmake构建,主要的工程代码在glog/src目录下
其中 glog/src/glog/里面是头文件的模板文件,除了glog/src/glog文件夹外,glog/src里面有.cc后缀的源码实现文件
这个是一个模板文件,经过autotool 处理后得到 log/logging.h,即我们在使用glog时需要include的头文件
与这个头文件配套的是 glog/src/logging.cc
logging.h里的内容:
在使用glog时,需要在main()里调用 InitGoogleLogging(argv[0])
其中 argv[0] 为进程第0 个参数,是程序的全名,
比如: ./test -v argv[0] -> ./test
根据下面的源码,InitGoogleLogging函数的作用就是将 test这字符串赋值给 全局变量 g_program_invocation_short_name
void InitGoogleLogging(const char* argv0) {
glog_internal_namespace_::InitGoogleLoggingUtilities(argv0);
}
void InitGoogleLoggingUtilities(const char* argv0) {
// 如果InitGoogleLogging被调用两次,则打印报错
CHECK(!IsGoogleLoggingInitialized())
<< "You called InitGoogleLogging() twice!";
// strrchr() 函数查找字符在指定字符串中从右面开始的第一次出现的位置,
// 如果成功,返回该字符以及其后面的字符,如果失败,则返回 NULL
const char* slash = strrchr(argv0, '/');
#ifdef GLOG_OS_WINDOWS
if (!slash) slash = strrchr(argv0, '\\');
#endif
// 例如./test, 则 g_program_invocation_short_name = test
g_program_invocation_short_name = slash ? slash + 1 : argv0;
#ifdef HAVE_STACKTRACE
InstallFailureFunction(&DumpStackTraceAndExit);
#endif
}
bool IsGoogleLoggingInitialized() {
return g_program_invocation_short_name != NULL;
}
const int GLOG_INFO = 0, GLOG_WARNING = 1, GLOG_ERROR = 2, GLOG_FATAL = 3,
NUM_SEVERITIES = 4;
#ifndef GLOG_NO_ABBREVIATED_SEVERITIES
# ifdef ERROR
# error ERROR macro is defined. Define GLOG_NO_ABBREVIATED_SEVERITIES before including logging.h. See the document for detail.
# endif
const int INFO = GLOG_INFO, WARNING = GLOG_WARNING,
ERROR = GLOG_ERROR, FATAL = GLOG_FATAL;
#endif
// severity: 严重性,表示日志等级
#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()
即LOG(INFO) 展开为:COMPACT_GOOGLE_LOG_INFO.stream()
根据下面的源码,COMPACT_GOOGLE_LOG_INFO也是一个宏,其展开为:
#if GOOGLE_STRIP_LOG == 0
#define COMPACT_GOOGLE_LOG_INFO @ac_google_namespace@::LogMessage( \
__FILE__, __LINE__)
#else
#define COMPACT_GOOGLE_LOG_INFO @ac_google_namespace@::NullStream()
#endif
LogMessage的定义如下:LogMessage是一个类
class GLOG_EXPORT LogMessage {
public:
enum {
// Passing kNoLogPrefix for the line number disables the
// log-message prefix. Useful for using the LogMessage
// infrastructure as a printing utility. See also the --log_prefix
// flag for controlling the log-message prefix on an
// application-wide basis.
kNoLogPrefix = -1
};
// LogStream inherit from non-DLL-exported class (std::ostrstream)
// and VC++ produces a warning for this situation.
// However, MSDN says "C4275 can be ignored in Microsoft Visual C++
// 2005 if you are deriving from a type in the Standard C++ Library"
// http://msdn.microsoft.com/en-us/library/3tdb471s(VS.80).aspx
// Let's just ignore the warning.
GLOG_MSVC_PUSH_DISABLE_WARNING(4275)
class GLOG_EXPORT LogStream : public std::ostream {
GLOG_MSVC_POP_WARNING()
public:
LogStream(char *buf, int len, int64 ctr)
: std::ostream(NULL),
streambuf_(buf, len),
ctr_(ctr),
self_(this) {
rdbuf(&streambuf_);
}
int64 ctr() const { return ctr_; }
void set_ctr(int64 ctr) { ctr_ = ctr; }
LogStream* self() const { return self_; }
// Legacy std::streambuf methods.
size_t pcount() const { return streambuf_.pcount(); }
char* pbase() const { return streambuf_.pbase(); }
char* str() const { return pbase(); }
private:
LogStream(const LogStream&);
LogStream& operator=(const LogStream&);
base_logging::LogStreamBuf streambuf_;
int64 ctr_; // Counter hack (for the LOG_EVERY_X() macro)
LogStream *self_; // Consistency check hack
};
public:
// icc 8 requires this typedef to avoid an internal compiler error.
typedef void (LogMessage::*SendMethod)();
LogMessage(const char* file, int line, LogSeverity severity, int64 ctr,
SendMethod send_method);
// Two special constructors that generate reduced amounts of code at
// LOG call sites for common cases.
// Used for LOG(INFO): Implied are:
// severity = INFO, ctr = 0, send_method = &LogMessage::SendToLog.
//
// Using this constructor instead of the more complex constructor above
// saves 19 bytes per call site.
LogMessage(const char* file, int line);
// Used for LOG(severity) where severity != INFO. Implied
// are: ctr = 0, send_method = &LogMessage::SendToLog
//
// Using this constructor instead of the more complex constructor above
// saves 17 bytes per call site.
LogMessage(const char* file, int line, LogSeverity severity);
// Constructor to log this message to a specified sink (if not NULL).
// Implied are: ctr = 0, send_method = &LogMessage::SendToSinkAndLog if
// also_send_to_log is true, send_method = &LogMessage::SendToSink otherwise.
LogMessage(const char* file, int line, LogSeverity severity, LogSink* sink,
bool also_send_to_log);
// Constructor where we also give a vector pointer
// for storing the messages (if the pointer is not NULL).
// Implied are: ctr = 0, send_method = &LogMessage::SaveOrSendToLog.
LogMessage(const char* file, int line, LogSeverity severity,
std::vector<std::string>* outvec);
// Constructor where we also give a string pointer for storing the
// message (if the pointer is not NULL). Implied are: 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 CheckOpString& result);
~LogMessage();
// Flush a buffered message to the sink set in the constructor. Always
// called by the destructor, it may also be called from elsewhere if
// needed. Only the first call is actioned; any later ones are ignored.
void Flush();
// An arbitrary limit on the length of a single log message. This
// is so that streaming can be done more efficiently.
static const size_t kMaxLogMessageLen;
// Theses should not be called directly outside of logging.*,
// only passed as SendMethod arguments to other LogMessage methods:
void SendToLog(); // Actually dispatch to the logs
void SendToSyslogAndLog(); // Actually dispatch to syslog and the logs
// Call abort() or similar to perform LOG(FATAL) crash.
static void @ac_cv___attribute___noreturn@ Fail();
std::ostream& stream();
int preserved_errno() const;
// Must be called without the log_mutex held. (L < log_mutex)
static int64 num_messages(int severity);
const LogMessageTime& getLogMessageTime() const;
struct LogMessageData;
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(glog_internal_namespace_::CrashReason* reason);
// Counts of messages sent at each priority:
static int64 num_messages_[NUM_SEVERITIES]; // under log_mutex
// We keep the data in a separate struct so that each instance of
// LogMessage uses less stack space.
LogMessageData* allocated_;
LogMessageData* data_;
LogMessageTime logmsgtime_;
friend class LogDestination;
LogMessage(const LogMessage&);
void operator=(const LogMessage&);
};
LogMessage.stream(): 这里就构造了一个google::LogMessage的临时对象,语句执行完就会自动析构
LogMessage::LogMessage(const char* file, int line)
: allocated_(NULL) {
Init(file, line, GLOG_INFO, &LogMessage::SendToLog);
}
ostream& LogMessage::stream() {
// data_是LogMessage的类成员变量
// LogMessageData* data_;
return data_->stream_;
}
在调用 LOG(INFO)时,创建一个临时LogMessage类对象,并把需要写入日志的信息写入到其成员变量 data_中,在LogMessage类对象退出作用域析构时执行析构函数(LogMessage的析构函数干了很重要的活),调用SendToLog()方法进行落盘(是否要真的落盘取决于缓存的日志字符串数或者缓存的时间)
在 临时对象LogMessage退出作用域时,执行其析构函数 ~LogMessage()
其中关键函数是 Flush()函数
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)
}
Flush()函数:
关键代码是 (this->*(data_->send_method_))(); send_method_ 即LogMessage::SendToLog()
// Flush buffered message, called by the destructor, or any other function
// that needs to synchronize the log.
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 we do need to add a \n, we'll do it by violating the memory of the
// ostrstream buffer. This is quick, and we'll make sure to undo our
// modification before anything else is done with the ostrstream. It
// would be preferable not to do things this way, but it seems to be
// the best way to deal with this.
if (append_newline) {
original_final_char = data_->message_text_[data_->num_chars_to_log_];
data_->message_text_[data_->num_chars_to_log_++] = '\n';
}
data_->message_text_[data_->num_chars_to_log_] = '\0';
// Prevent any subtle race conditions by wrapping a mutex lock around
// the actual logging action per se.
{
MutexLock l(&log_mutex);
(this->*(data_->send_method_))();
++num_messages_[static_cast<int>(data_->severity_)];
}
LogDestination::WaitForSinks(data_);
if (append_newline) {
// Fix the ostrstream back how it was before we screwed with it.
// It's 99.44% certain that we don't need to worry about doing this.
data_->message_text_[data_->num_chars_to_log_-1] = original_final_char;
}
// If errno was already set before we enter the logging call, we'll
// set it back to that value when we return from the logging call.
// It happens often that we log an error message after a syscall
// failure, which can potentially set the errno to some other
// values. We would like to preserve the original errno.
if (data_->preserved_errno_ != 0) {
errno = data_->preserved_errno_;
}
// Note that this message is now safely logged. If we're asked to flush
// again, as a result of destruction, say, we'll do nothing on future calls.
data_->has_been_flushed_ = true;
}
LogMessage::SendToLog()定义如下:
其中调用了静态方法如 LogDestination::LogToAllLogfiles()
void LogMessage::SendToLog() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) {
static bool already_warned_before_initgoogle = false;
log_mutex.AssertHeld();
RAW_DCHECK(data_->num_chars_to_log_ > 0 &&
data_->message_text_[data_->num_chars_to_log_-1] == '\n', "");
// Messages of a given severity get logged to lower severity logs, too
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;
}
// global flag: never log to file if set. Also -- don't log to a
// file if we haven't parsed the command line flags to get the
// program name.
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_);
}
// this could be protected by a flag if necessary.
LogDestination::LogToSinks(data_->severity_,
data_->fullname_, data_->basename_,
data_->line_, logmsgtime_,
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_, logmsgtime_.timestamp(),
data_->message_text_,
data_->num_chars_to_log_);
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_, logmsgtime_,
data_->message_text_ + data_->num_prefix_chars_,
(data_->num_chars_to_log_
- data_->num_prefix_chars_ - 1) );
// NOTE: -1 removes trailing \n
}
LogDestination::LogToAllLogfiles:
其内部调用了 LogDestination::MaybeLogToLogfile
核心为 destination->logger_->Write(should_flush, timestamp, message, len);
inline void LogDestination::LogToAllLogfiles(LogSeverity severity,
time_t 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(i, timestamp, message, len);
}
}
}
inline void LogDestination::MaybeLogToLogfile(LogSeverity severity,
time_t timestamp,
const char* message,
size_t len) {
const bool should_flush = severity > FLAGS_logbuflevel;
LogDestination* destination = log_destination(severity);
destination->logger_->Write(should_flush, timestamp, message, len);
}
destination->logger_->Write(should_flush, timestamp, message, len)如下:
这里会执行:
● 日志写入page cache
● 日志落盘
● 一些其他操作
void LogFileObject::Write(bool force_flush,
time_t timestamp,
const char* message,
size_t message_len) {
MutexLock l(&lock_);
// We don't log if the base_name_ is "" (which means "don't write")
if (base_filename_selected_ && base_filename_.empty()) {
return;
}
if (file_length_ >> 20U >= MaxLogSize() || PidHasChanged()) {
if (file_ != NULL) fclose(file_);
file_ = NULL;
file_length_ = bytes_since_flush_ = dropped_mem_length_ = 0;
rollover_attempt_ = kRolloverAttemptFrequency - 1;
}
// If there's no destination file, make one before outputting
if (file_ == NULL) {
// Try to rollover the log file every 32 log messages. The only time
// this could matter would be when we have trouble creating the log
// file. If that happens, we'll lose lots of log messages, of course!
if (++rollover_attempt_ != kRolloverAttemptFrequency) return;
rollover_attempt_ = 0;
struct ::tm tm_time;
if (FLAGS_log_utc_time) {
gmtime_r(×tamp, &tm_time);
} else {
localtime_r(×tamp, &tm_time);
}
// The logfile's filename will have the date/time & pid in it
ostringstream time_pid_stream;
time_pid_stream.fill('0');
time_pid_stream << 1900+tm_time.tm_year
<< setw(2) << 1+tm_time.tm_mon
<< setw(2) << tm_time.tm_mday
<< '-'
<< setw(2) << tm_time.tm_hour
<< setw(2) << tm_time.tm_min
<< setw(2) << tm_time.tm_sec
<< '.'
<< GetMainThreadPid();
const string& time_pid_string = time_pid_stream.str();
if (base_filename_selected_) {
if (!CreateLogfile(time_pid_string)) {
perror("Could not create log file");
fprintf(stderr, "COULD NOT CREATE LOGFILE '%s'!\n",
time_pid_string.c_str());
return;
}
} else {
// If no base filename for logs of this severity has been set, use a
// default base filename of
// "...log..". So
// logfiles will have names like
// webserver.examplehost.root.log.INFO.19990817-150000.4354, where
// 19990817 is a date (1999 August 17), 150000 is a time (15:00:00),
// and 4354 is the pid of the logging process. The date & time reflect
// when the file was created for output.
//
// Where does the file get put? Successively try the directories
// "/tmp", and "."
string stripped_filename(
glog_internal_namespace_::ProgramInvocationShortName());
string hostname;
GetHostName(&hostname);
string uidname = MyUserName();
// We should not call CHECK() here because this function can be
// called after holding on to log_mutex. We don't want to
// attempt to hold on to the same mutex, and get into a
// deadlock. Simply use a name like invalid-user.
if (uidname.empty()) uidname = "invalid-user";
stripped_filename = stripped_filename+'.'+hostname+'.'
+uidname+".log."
+LogSeverityNames[severity_]+'.';
// We're going to (potentially) try to put logs in several different dirs
const vector<string> & log_dirs = GetLoggingDirectories();
// Go through the list of dirs, and try to create the log file in each
// until we succeed or run out of options
bool success = false;
for (vector<string>::const_iterator dir = log_dirs.begin();
dir != log_dirs.end();
++dir) {
base_filename_ = *dir + "/" + stripped_filename;
if ( CreateLogfile(time_pid_string) ) {
success = true;
break;
}
}
// If we never succeeded, we have to give up
if ( success == false ) {
perror("Could not create logging file");
fprintf(stderr, "COULD NOT CREATE A LOGGINGFILE %s!",
time_pid_string.c_str());
return;
}
}
// Write a header message into the log file
if (FLAGS_log_file_header) {
ostringstream file_header_stream;
file_header_stream.fill('0');
file_header_stream << "Log file created at: "
<< 1900+tm_time.tm_year << '/'
<< setw(2) << 1+tm_time.tm_mon << '/'
<< setw(2) << tm_time.tm_mday
<< ' '
<< setw(2) << tm_time.tm_hour << ':'
<< setw(2) << tm_time.tm_min << ':'
<< setw(2) << tm_time.tm_sec << (FLAGS_log_utc_time ? " UTC\n" : "\n")
<< "Running on machine: "
<< LogDestination::hostname() << '\n';
if(!g_application_fingerprint.empty()) {
file_header_stream << "Application fingerprint: " << g_application_fingerprint << '\n';
}
const char* const date_time_format = FLAGS_log_year_in_prefix
? "yyyymmdd hh:mm:ss.uuuuuu"
: "mmdd hh:mm:ss.uuuuuu";
file_header_stream << "Running duration (h:mm:ss): "
<< PrettyDuration(static_cast<int>(WallTime_Now() - start_time_)) << '\n'
<< "Log line format: [IWEF]" << date_time_format << " "
<< "threadid file:line] msg" << '\n';
const string& file_header_string = file_header_stream.str();
const size_t header_len = file_header_string.size();
fwrite(file_header_string.data(), 1, header_len, file_);
file_length_ += header_len;
bytes_since_flush_ += header_len;
}
}
// Write to LOG file
if ( !stop_writing ) {
// fwrite() doesn't return an error when the disk is full, for
// messages that are less than 4096 bytes. When the disk is full,
// it returns the message length for messages that are less than
// 4096 bytes. fwrite() returns 4096 for message lengths that are
// greater than 4096, thereby indicating an error.
errno = 0;
fwrite(message, 1, message_len, file_);
if ( FLAGS_stop_logging_if_full_disk &&
errno == ENOSPC ) { // disk full, stop writing to disk
stop_writing = true; // until the disk is
return;
} else {
file_length_ += message_len;
bytes_since_flush_ += message_len;
}
} else {
if (CycleClock_Now() >= next_flush_time_) {
stop_writing = false; // check to see if disk has free space.
}
return; // no need to flush
}
// See important msgs *now*. Also, flush logs at least every 10^6 chars,
// or every "FLAGS_logbufsecs" seconds.
if ( force_flush ||
(bytes_since_flush_ >= 1000000) ||
(CycleClock_Now() >= next_flush_time_) ) {
FlushUnlocked();
#ifdef GLOG_OS_LINUX
// Only consider files >= 3MiB
if (FLAGS_drop_log_memory && file_length_ >= (3U << 20U)) {
// Don't evict the most recent 1-2MiB so as not to impact a tailer
// of the log file and to avoid page rounding issue on linux < 4.7
uint32 total_drop_length =
(file_length_ & ~((1U << 20U) - 1U)) - (1U << 20U);
uint32 this_drop_length = total_drop_length - dropped_mem_length_;
if (this_drop_length >= (2U << 20U)) {
// Only advise when >= 2MiB to drop
# if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21)
// 'posix_fadvise' introduced in API 21:
// * https://android.googlesource.com/platform/bionic/+/6880f936173081297be0dc12f687d341b86a4cfa/libc/libc.map.txt#732
# else
posix_fadvise(fileno(file_), static_cast<off_t>(dropped_mem_length_),
static_cast<off_t>(this_drop_length),
POSIX_FADV_DONTNEED);
# endif
dropped_mem_length_ = total_drop_length;
}
}
#endif
// Remove old logs
if (log_cleaner.enabled()) {
log_cleaner.Run(base_filename_selected_,
base_filename_,
filename_extension_);
}
}
}
在日志落盘时主要时执行LogFileObject::FlushUnlocked()函数,在该函数里关键是执行fflush库函数,将之前写入到page cache中的日志信息刷新到磁盘中去。
fflush是一个在C语言标准输入输出库中的函数,功能是冲洗流中的信息,该函数通常用于处理磁盘文件。fflush()会强迫将缓冲区内的数据写回参数stream 指定的文件中。
void LogFileObject::FlushUnlocked(){
if (file_ != NULL) {
fflush(file_);
bytes_since_flush_ = 0;
}
// Figure out when we are due for another flush.
const int64 next = (FLAGS_logbufsecs
* static_cast<int64>(1000000)); // in usec
next_flush_time_ = CycleClock_Now() + UsecToCycles(next);
}
所以可以看出,glog是个同步类型的日志库,并不支持异步日志。