在学习 chromium 源码时,我们经常需要增加调试日志,常见的用法一般是
// TurboNet.mm
133
134 LOG(INFO) << "TurboNet Engine started.";
135 LOG(INFO);
136 LOG(WARNING);
137 VLOG(1);
138 VLOG(2);
139 PLOG(INFO); // 会输出系统错误码信息
140 PLOG(WARNING);
141 DLOG(INFO);
142 DLOG(WARNING);
日志输出效果如下:
其中 INFO 代表当前这条日志的级别(详见下面 2 小节),使用的时候就是输入 INFO 就行。接下来我们在探索下这个宏背后的内容。 LOG 在 release 和 debug 模式均生效,DLOG 仅在 debug 模式生效。VLOG 支持模式匹配,比如指定特定文件的日志级别、特定目录的日志级别、特定前缀的文件的日志级别等,详见官方注释:
// There are "verbose level" logging macros. They look like
//
// VLOG(1) << "I'm printed when you run the program with --v=1 or more";
// VLOG(2) << "I'm printed when you run the program with --v=2 or more";
//
// These always log at the INFO log level (when they log at all).
// The verbose logging can also be turned on module-by-module. For instance,
// --vmodule=profile=2,icon_loader=1,browser_*=3,*/chromeos/*=4 --v=0
// will cause:
// a. VLOG(2) and lower messages to be printed from profile.{h,cc}
// b. VLOG(1) and lower messages to be printed from icon_loader.{h,cc}
// c. VLOG(3) and lower messages to be printed from files prefixed with
// "browser"
// d. VLOG(4) and lower messages to be printed from files under a
// "chromeos" directory.
// e. VLOG(0) and lower messages to be printed from elsewhere
一、基本用法
LOG 本身是一个宏:
#define LOG(severity) LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity))
我们逐个解开:
1、LOG_STREAM
#define LOG_STREAM(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()
LOG_STREAM(severity) 会被转成具体 severity 对应的宏,例如 INFO 被转为宏 COMPACT_GOOGLE_LOG_INFO:
#define COMPACT_GOOGLE_LOG_INFO COMPACT_GOOGLE_LOG_EX_INFO(LogMessage)
宏 COMPACT_GOOGLE_LOG_EX_INFO:
#define COMPACT_GOOGLE_LOG_EX_INFO(ClassName, ...) \
::logging::ClassName(__FILE__, __LINE__, ::logging::LOGGING_INFO, \
##__VA_ARGS__)
ClassName 是 LogMessage,因此这里 COMPACT_GOOGLE_LOG_EX_INFO 代表的是 ::logging::LogMessage 类的构造方法,构造方法里调用 Init 方法初始化了一些调试信息比如文件名,行号,时间戳等,保存在 std::ostringstream 类型的成员变量 stream_ 中,LOG_STREAM 这个宏最终就是返回了这个 stream_对象。
2、LOG_IS_ON
#define LOG_IS_ON(severity) (::logging::ShouldCreateLogMessage(::logging::LOGGING_##severity))
// severity 值如下
constexpr LogSeverity LOGGING_INFO = 0;
constexpr LogSeverity LOGGING_WARNING = 1;
constexpr LogSeverity LOGGING_ERROR = 2;
constexpr LogSeverity LOGGING_FATAL = 3;
constexpr LogSeverity LOGGING_DEBUG = 4;
constexpr LogSeverity LOGGING_NUM_SEVERITIES = 5;
bool ShouldCreateLogMessage(int severity) {
if (severity < g_min_log_level)
return false;
// Return true here unless we know ~LogMessage won't do anything.
return g_logging_destination != LOG_NONE || g_log_message_handler ||
severity >= kAlwaysPrintErrorLevel;
}
// g_min_log_level
// 如果 SetMinLogLevel 未调用,g_min_log_level 默认为 LOGGING_FATAL
// 因此为了输出 LOGGING_INFO 级别的日志,需要将 g_min_log_level 的值设置为 <=-1 的值
void SetMinLogLevel(int level) {
g_min_log_level = std::min(LOGGING_FATAL, level);
}
3、LAZY_STREAM
#define LAZY_STREAM(stream, condition) !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream)
如果 condition 为 false,则返回 nullptr,否则返回 stream 对象,这里 ::logging::LogMessageVoidify() 是一个空操作,该类内部重载了 & 运算符,对应一个空函数体,这么做是为了避免编译器警告 stream 未被使用。
二、进阶用法【未完待续】
VLOG、PLOG、DLOG
1、VLOG
整体跟 LOG 差不多,使用方式如下:
VLOG(1) << "Filter function already exists: " << filter_function
<< " with associated data: " << user_data;
#define VLOG(verbose_level) \
LAZY_STREAM(VLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level))
// 除了对日志级别取负值,跟 LOG 一样,
#define VLOG_STREAM(verbose_level) \
::logging::LogMessage(__FILE__, __LINE__, -(verbose_level)).stream()
#define VLOG_IS_ON(verboselevel) \
((verboselevel) <= ::logging::GetVlogLevel(__FILE__))
template <size_t N>
int GetVlogLevel(const char (&file)[N]) {
return GetVlogLevelHelper(file, N);
}
int GetVlogLevelHelper(const char* file, size_t N) {
DCHECK_GT(N, 0U);
// Note: |g_vlog_info| may change on a different thread during startup
// (but will always be valid or nullptr).
VlogInfo* vlog_info = g_vlog_info;
return vlog_info ?
vlog_info->GetVlogLevel(base::StringPiece(file, N - 1)) :
GetVlogVerbosity();
}
GetVlogLevel 会根据预设的模式对 file 进行匹配,匹配成功则可以执行日志输出,否则不执行。
三、日志输出
在 LogMessage::~LogMessage() 方法中,我们可以看到对各个系统平台的调试日志输出做了兼容,例如安卓(调用 __android_log_write)、iOS(调用__builtin_os_log_format)、Win(调用 OutputDebugStringA)等,因此鸿蒙的支持也需要在该方法中完成。
参考资料