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時也創建了流對象,雖然沒有“工作”,但也還是“勤快”的,哈哈!