chromium中logging源碼閱讀記錄

Qt源碼閱讀中偶然間看到webegine引用了chromium的源碼,應該說webengigne的實現是基於chromium源碼做了一層封裝。看了下爲通用的base庫log的實現,做一下記錄。
chromium的日誌記錄的實現比較簡單,可配置性比較弱,不像log4xx各種複雜的配置及文件和文件拆分等。從源碼實現上,可以看出其日誌記錄的特性:

  1. 當前進程僅支持一個日誌文件,默認名稱爲debug.log。可指定文件路徑,進程再次啓動時若同名文件已存在則追加寫入;
  2. 所有等級的日誌記錄在同一文件,不支持按等級分文件記錄;

實現上,核心類型爲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))

宏逐層展開其實就兩個過程:

  1. ::logging::LogMessage(FILE, LINE, -verbose_level)構造日誌對象;
  2. .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時也創建了流對象,雖然沒有“工作”,但也還是“勤快”的,哈哈!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章