Chromium许多日志被TraceEvent代替了,因此TraceEvent出现的频率要比Log高很多。
但是也有不少场景使用Log。
在blink,Log的实现由base提供,而blink/render/core/base/logging.h进行了二次封装。
日志系统的设计细节
错误对话框处理
- 错误消息显示: 当应用程序遇到断言失败或致命错误时,默认情况下会弹出一个对话框展示错误信息。然而,这个UI的创建会导致消息循环,这可能使应用在不好的状态中处理或分发消息到现有窗口,导致挂起或异常行为。
- 分离进程显示错误: 为了避免上述问题,可以在与主应用程序相同的目录下创建一个名为
DebugMessage.exe
的独立程序,用于专门显示致命错误对话框。这样即使主程序处于不稳定状态,错误信息也能正确显示而不影响主程序的其他部分。 - 简化命令行解析: 此独立程序接收错误信息作为命令行参数,不会包含应用程序名称,以便于解析。
日志宏
- 基本日志: 提供了一系列宏,如
LOG(INFO)
,用于在不同严重级别下记录日志信息。 - 条件日志: 可以使用
LOG_IF(INFO, condition)
根据条件记录日志。 - 断言宏:
CHECK(condition)
在所有构建模式下都有效,如果条件不满足,则记录FATAL级别的日志并终止程序。
调试模式日志
- 调试宏:
DLOG(INFO)
仅在调试模式下生效,而在非调试模式下会被编译器忽略。 - 调试断言:
DLOG_ASSERT(condition)
在调试模式下工作,类似于CHECK
。
详细日志
- 详细级别日志:
VLOG(1)
允许根据不同的详细级别记录日志,可以通过运行参数--v=<level>
开启。 - 模块特定日志: 可以指定特定模块的详细级别,例如
--vmodule=profile=2
。 - 通配符支持: 模块名可以使用通配符
*
和?
匹配多个文件或目录下的源文件。
其他特性
- 系统错误日志:
PLOG(ERROR)
附加系统错误信息到日志中。 - 特殊日志级别:
DFATAL
在DCHECK
启用的构建中等同于FATAL
,在普通构建中为ERROR
。 - 格式化输出: 日志输出包括PID、TID、日期时间、日志级别、文件名及行号等信息。
配置和偏好设置
- 用户可以通过
SetLogItems()
函数调整日志的可见性。 - 在Chrome OS上,日志输出可以切换到syslog-like格式。
LOG_STREAM
在实际打印日志的时候,是以流式打印的,例如:
LOG(INFO) << "Found " << num_cookies << " cookies";
LOG宏定义如下:
// We use the preprocessor's merging operator, "##", so that, e.g.,
// LOG(INFO) becomes the token COMPACT_GOOGLE_LOG_INFO. There's some funny
// subtle difference between ostream member streaming functions (e.g.,
// ostream::operator<<(int) and ostream non-member streaming functions
// (e.g., ::operator<<(ostream&, string&): it turns out that it's
// impossible to stream something like a string directly to an unnamed
// ostream. We employ a neat hack by calling the stream() member
// function of LogMessage which seems to avoid the problem.
#define LOG_STREAM(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()
#define LOG(severity) LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity))
#define LOG_IF(severity, condition) \
LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity) && (condition))
// The VLOG macros log with negative verbosities.
#define VLOG_STREAM(verbose_level) \
::logging::LogMessage(__FILE__, __LINE__, -(verbose_level)).stream()
这段代码的解读如下:
-
LOG_STREAM 宏:
#define LOG_STREAM(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()
这个宏接受一个参数
severity
,它是一个日志级别(例如,INFO、ERROR等)。宏中的##
是C++预处理器的字符串拼接操作符,它会将两个独立的标识符拼接成一个新的标识符。因此,LOG_STREAM(severity)
被展开为COMPACT_GOOGLE_LOG_severity.stream()
。 -
LOG 和 LOG_IF 宏:
#define LOG(severity) LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity)) #define LOG_IF(severity, condition) \ LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity) && (condition))
这些宏用于实际的日志输出。
LOG
和LOG_IF
都会调用LAZY_STREAM
宏,其中LOG_STREAM(severity)
提供了日志流对象,而LOG_IS_ON(severity)
检查日志级别是否被启用。LOG_IF
则额外检查一个条件condition
,只有当这个条件为真时才进行日志记录。 -
VLOG 和 VLOG_STREAM 宏:
#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))
VLOG
宏用于更详细的日志记录,通常用于调试。VLOG
和VLOG_STREAM
接受一个verbose_level
参数,表示日志的详细程度。VLOG_STREAM
创建一个LogMessage
对象,并通过传递文件名、行号和负数的verbose_level
来控制日志的详细度。VLOG
宏同样使用LAZY_STREAM
来决定是否真正执行日志记录,这依赖于VLOG_IS_ON(verbose_level)
的值。这种设计允许在编译时优化掉未启用的日志输出,从而提高运行时性能。
base 的 Log
Chromium的base模块里也有一个Log模块
【未完待续 … 】
absel的Log
在一些底层模块,更倾向于直接调用abseil-cpp\absl\log的日志模块,其使用方式也和上面大同小异。宏展开之后如下:
为什么这个宏要用switch-case的形式呢?这里有说明:
// `ABSL_LOG_INTERNAL_CONDITION` prefixes another macro that expands to a
// temporary `LogMessage` instantiation followed by zero or more streamed
// expressions. This definition is tricky to read correctly. It evaluates to
// either
//
// (void)0;
//
// or
//
// ::absl::log_internal::Voidify() &&
// ::absl::log_internal::LogMessage(...) << "the user's message";
//
// If the condition is evaluable at compile time, as is often the case, it
// compiles away to just one side or the other.
//
// Although this is not used anywhere a statement (e.g. `if`) could not go,
// the ternary expression does a better job avoiding spurious diagnostics
// (dangling else, missing switch case) and preserving noreturn semantics (e.g.
// on `LOG(FATAL)`) without requiring braces.
//
// The `switch` ensures that this expansion is the begnning of a statement (as
// opposed to an expression) and prevents shenanigans like
// `AFunction(LOG(INFO))` and `decltype(LOG(INFO))`. The apparently-redundant
// `default` case makes the condition more amenable to Clang dataflow analysis.
#define ABSL_LOG_INTERNAL_STATELESS_CONDITION(condition) \
switch (0) \
case 0: \
default: \
!(condition) ? (void)0 : ::absl::log_internal::Voidify()&&
简而言之,switch (0)确保宏展开后的代码始终从一个合法的语句开始,而不是表达式。这防止了诸如AFunction(LOG(INFO))或decltype(LOG(INFO))这样的非法用法。
case 0后面跟default,是为了默认情况使条件更适合Clang执行流分析,使得代码被优化得更好。
日志的写入架构
日志没什么缓存,判断认为该写文件的就马上写文件了,这样的设计也是为了避免崩溃的时候日志不全。缓存的事情应该由操作系统的文件系统去处理: