C++层
首先我们分析一下,以形如LOG(ERROR) << "Oops! Error";这样的方式在.so里面试图打日志的时候,实际都发生了什么。在base/logging.h文件中含有各个与调试相关宏的定义,从中我们可以看到LOG()这个宏的代码如下:
:::C++
#define COMPACT_GOOGLE_LOG_EX_ERROR(ClassName, ...) \
logging::ClassName(__FILE__, __LINE__, logging::LOG_ERROR, ##__VA_ARGS__)
#define COMPACT_GOOGLE_LOG_ERROR \
COMPACT_GOOGLE_LOG_EX_ERROR(LogMessage)
#define LAZY_STREAM(stream, condition) \
!(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream)
#define LOG_STREAM(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()
#define LOG_IS_ON(severity) \
(::logging::ShouldCreateLogMessage(::logging::LOG_##severity))
#define LOG(severity) LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity))
那么首先,LOG()调用的是LAZY_STREAM(),这个宏只是一层简单的Wrapper,用来在打日志条件不满足的时候,不要去进行任何的求值以避免无意义的计算。那么实际起到控制作用的就是LOG_STREAM以及LOG_IS_ON这俩宏:前者用来产生一个可写的流对象,而后者则是控制是不是真要往这个流里面写入数据。
LOG_IS_ON宏的内部实际上是调用一个函数ShouldCreateLogMessage(),它检查一个全局变量g_min_log_level,这个值标志着当前的全局Log Level。通过设置这个变量的值,我们能够控制所有C++代码中日志语句的实际执行效果:比如这个值设置得很高,那么就只有Fatal Error之类的日志语句才能真的打出日志来,低级别的日志就会被忽略掉了。
我们再将目光转向LOG_STREAM宏,通过一层Wrapper中转,它真正执行的是这样一行调用:
logging::ClassName(__FILE__, __LINE__, logging::LOG_ERROR, ##__VA_ARGS__)
而这行调用中的ClassName现在是写死的LogMessage类。那么实际上,这就是以当前的文件名、行号以及日志级别这三个参数,来实例化了一个logging::LogMessage对象而已。这个对象在构造的过程中会调用自身的Init()方法,所做的事情则是根据构造时传入的参数来拼接日志字符串,作为前缀写入std::ostringstream对象而已。最后这个stream会作为LOG(ERROR)的返回值返回,然后自己的日志字符串就能够插入这个流了。
但现在问题是,我们构造了一个流并插入了字符串数据,然而这些数据去哪里了呢?理论上,栈上新建一个对象如果不操作的话,到作用域之外直接就析构掉了,那么显然可见,对日志字符串的处理是放到析构函数里面去做了。打开析构函数,可以看到它做了几件事情:如果定义有日志信息的钩子函数,那么就把日志信息交给钩子函数处理;如果没有,则会根据操作系统平台的不同来选择相应的处理方式。对于Android系统而言,会调用__android_log_write这个Android库函数,将日志以chromium这个tag来写入logcat;此外,如果启用了将日志写入文件的功能的话,也会执行相应的操作。
Java层
先前我们提到,全局变量g_min_log_level控制着日志是否真的能够打出来。那么就必然存在着设置这个值的方法:SetMinLogLevel()。事实上,在这个方法之上,有着相应的Java JNI接口封装,其代码如下:
:::C++
static jint SetMinLogLevel(JNIEnv* env,
const JavaParamRef<jclass>& jcaller,
jint jlog_level) {
jint old_log_level = static_cast<jint>(logging::GetMinLogLevel());
// MinLogLevel is global, shared by all URLRequestContexts.
logging::SetMinLogLevel(static_cast<int>(jlog_level));
return old_log_level;
}
由此可见,对于cronet这个库而言,在Java应用层启动App的时候,会调用nativeSetMinLogLevel()方法来设置全局的日志级别,而从下面这段代码中可以看出,这个日志级别实际上是来源于函数getLoggingLevel()的返回值:
:::Java
CronetLibraryLoader.ensureInitialized(builder.getContext(), builder);
nativeSetMinLogLevel(getLoggingLevel());
那么至此,我们基本已经理清Java层是如何决定cronet库的日志级别的了,就是通过这个getLoggingLevel()函数。那么我们就来看一下它的实现:
:::Java
private int getLoggingLevel() {
int loggingLevel;
if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
loggingLevel = LOG_VERBOSE;
} else if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
loggingLevel = LOG_DEBUG;
} else {
loggingLevel = LOG_NONE;
}
return loggingLevel;
}
这里可以看到,getLoggingLevel()通过调用Log.isLoggable()函数(这是个Android系统库函数),读取了系统对某个Log Tag所设置的log级别,然后根据这个设置,来决定libcronet.so的日志级别。而事实上在cronet的Java接口中定义了LOG_TAG = "ChromiumNetwork",因此,通过下面这行语句,我们就能够将系统对于ChromiumNetwork这个日志Tag的级别调低至VERBOSE,从而顺利打印出cronet库的所有日志。
adb shell setprop log.tag.ChromiumNetwork VERBOSE
总结
综上所述,如果需要临时启用cronet库的日志机制,那么我们可以直接使用上面的adb指令来操作Android日志属性;而如果是想要永久性打开详细日志,自己在C++代码中自行控制cronet的日志打印情况,那么我们只需要在getLoggingLevel()函数中将默认的日志级别改为LOG_VERBOSE即可。