Qt源码阅读中偶然间看到webegine引用了chromium的源码,应该说webengigne的实现是基于chromium源码做了一层封装。看了下为通用的base库log的实现,做一下记录。
chromium的日志记录的实现比较简单,可配置性比较弱,不像log4xx各种复杂的配置及文件和文件拆分等。从源码实现上,可以看出其日志记录的特性:
- 当前进程仅支持一个日志文件,默认名称为debug.log。可指定文件路径,进程再次启动时若同名文件已存在则追加写入;
- 所有等级的日志记录在同一文件,不支持按等级分文件记录;
实现上,核心类型为LogMessage,其实现如下:
// This class more or less represents a particular log message. You
// create an instance of LogMessage and then stream stuff to it.
// When you finish streaming to it, ~LogMessage is called and the
// full message gets streamed to the appropriate destination.
//
// You shouldn't actually use LogMessage's constructor to log things,
// though. You should use the LOG() macro (and variants thereof)
// above.
class BASE_EXPORT LogMessage {
public:
// Used for LOG(severity).
LogMessage(const char* file, int line, LogSeverity severity);
// Used for CHECK(). Implied severity = LOG_FATAL.
LogMessage(const char* file, int line, const char* condition);
// Used for CHECK_EQ(), etc. Takes ownership of the given string.
// Implied severity = LOG_FATAL.
LogMessage(const char* file, int line, std::string* result);
// Used for DCHECK_EQ(), etc. Takes ownership of the given string.
LogMessage(const char* file, int line, LogSeverity severity,
std::string* result);
~LogMessage();
//对外提供流对象,供日志信息的追加用
std::ostream& stream() { return stream_; }
LogSeverity severity() { return severity_; }
//将流对象中缓存的字符串取出,供日志文件写入
std::string str() { return stream_.str(); }
private:
// 构造函数中调用,将每条日志的公共头部,如文件名称、行号、时间写入字符串流;
void Init(const char* file, int line);
LogSeverity severity_;
std::ostringstream stream_; // 字符串流对象,作为日志输出内容的缓存用
size_t message_start_; // Offset of the start of the message (past prefix
// info).
// The file and line information passed in to the constructor.
const char* file_; // 文件名称、代码函数,实现中取对象定义时所在文件及文件行序号
const int line_;
// !!!删除SaveLastError
DISALLOW_COPY_AND_ASSIGN(LogMessage); //文件对象不支持复制,其实也没必要复制
};
//重点看一下析构函数:
//核心点就取流中的字符串,然后写文件
LogMessage::~LogMessage() {
size_t stack_start = stream_.tellp();
stream_ << std::endl;
std::string str_newline(stream_.str());
// Give any log message handler first dibs on the message.
if (log_message_handler &&
log_message_handler(severity_, file_, line_,
message_start_, str_newline)) {
// The handler took care of it, no further processing.
return;
}
// write to log file
if ((g_logging_destination & LOG_TO_FILE) != 0) {
// We can have multiple threads and/or processes, so try to prevent them
// from clobbering each other's writes.
// If the client app did not call InitLogging, and the lock has not
// been created do it now. We do this on demand, but if two threads try
// to do this at the same time, there will be a race condition to create
// the lock. This is why InitLogging should be called from the main
// thread at the beginning of execution.
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
LoggingLock::Init(LOCK_LOG_FILE, nullptr);
LoggingLock logging_lock;
#endif
//若文件未初始化,首先开文件初始化文件句柄
if (InitializeLogFileHandle()) {
#if defined(OS_WIN)
DWORD num_written;
//字符串流中取文本,写文件
WriteFile(g_log_file,
static_cast<const void*>(str_newline.c_str()),
static_cast<DWORD>(str_newline.length()),
&num_written,
nullptr);
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
ignore_result(fwrite(
str_newline.data(), str_newline.size(), 1, g_log_file));
fflush(g_log_file);
#else
#error Unsupported platform
#endif
}
}
日志记录库通常定义些宏,方便日志相关代码的书写,源码中如下:
//业务层使用的示例代码
// LOG(INFO) << "Found " << num_cookies << " cookies";
#define LOG(severity) LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity))
// Helper macro which avoids evaluating the arguments to a stream if
// the condition doesn't hold. Condition is evaluated once and only once.
#define LAZY_STREAM(stream, condition) \
!(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream)
// The VLOG macros log with negative verbosities.
#define VLOG_STREAM(verbose_level) \
::logging::LogMessage(__FILE__, __LINE__, -verbose_level).stream()
#define VLOG(verbose_level) \
LAZY_STREAM(VLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level))
宏逐层展开其实就两个过程:
- ::logging::LogMessage(FILE, LINE, -verbose_level)构造日志对象;
- .stream()取字符串流对象,写入日志字符串
LAZY_STREAM是中间的判断处理和转发控制,仅在日志等级符合条件的情况下才允许写入。
LogMessageVoidify的定义如下,该对象的主要作用是抑制宏定义中condition为单独变量时的编译警告。百度了下只有在linux平台编译时才会出现,所以未做具体的验证。
// This class is used to explicitly ignore values in the conditional
// logging macros. This avoids compiler warnings like "value computed
// is not used" and "statement has no effect".
class LogMessageVoidify {
public:
LogMessageVoidify() = default;
// This has to be an operator with a precedence lower than << but
// higher than ?:
constexpr void operator&(std::ostream&) { }
};
这里核心依赖的就是几个运算符的优先级。对几个优先级的测试验证如下:
struct FakeStream
{
public:
FakeStream()
{
std::cout << "FakeStream()" << std::endl;
}
FakeStream& operator<<(const string& str)
{
cout << "FakeStream::operator<<()" << std::endl;
return *this;
}
};
class LogMessage
{
public:
LogMessage()
{
std::cout << "LogMessage()" << std::endl;
}
FakeStream& Stream()
{
return stream;
}
private:
FakeStream stream;
};
struct VoidTest
{
public:
VoidTest()
{
std::cout << "VoidTest()" << std::endl;
}
void operator&(FakeStream&)
{
cout << "VoidTest::operator&()" << std::endl;
}
};
inline bool Condition()
{
std::cout << "Condition()" << std::endl;
//debug
return false;
}
int main()
{
FakeStream ss;
!Condition() ? (void)0 : VoidTest() & (LogMessage().Stream()) << "haha";
getchar();
return 0;
}
//Condition为false的输出
FakeStream()
Condition()
//Condition为true的输出
FakeStream()
Condition()
FakeStream()
LogMessage()
FakeStream::operator<<()
VoidTest()
VoidTest::operator&()
比较神,当条件为false,不需要日志记录时,只会创建流对象,不创建LogMessage对象,更不会向流写入内容,也就不会向文件中输出内容;当条件为true,创建日志对象及字符流输入,输出日志,此时注意条件表达式各部分执行的先后顺序。可见该条件表达式的设计和巧妙,能够有效的控制LogMessage的创建。从流对象的角度看,LAZY_STREAM不是很贴切,毕竟条件为false时也创建了流对象,虽然没有“工作”,但也还是“勤快”的,哈哈!