在学习 chromium 源码时,我们经常需要增加调试日志,常见的用法一般是
// TurboNet.mm
133
134 LOG(INFO) << "TurboNet Engine started.";
日志输出效果如下:
其中 INFO 代表当前这条日志的级别,使用的时候就是输入 INFO 就行。接下来我们在探索下这个宏背后的内容。
一、基本用法
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();
}
三、日志输出
在 LogMessage::~LogMessage() 方法中,我们可以看到对各个系统平台的调试日志输出做了兼容,例如安卓(调用 __android_log_write)、iOS(调用__builtin_os_log_format)、Win(调用 OutputDebugStringA)等,因此鸿蒙的支持也需要在该方法中完成。
参考资料