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时也创建了流对象,虽然没有“工作”,但也还是“勤快”的,哈哈!

发布了18 篇原创文章 · 获赞 3 · 访问量 9851

猜你喜欢

转载自blog.csdn.net/u011388696/article/details/104736555