Android14 Log.isLoggable判断的分析
文章目录
- Android14 Log.isLoggable判断的分析
- 一、前言
- 二、答案和分析
- 1、Log.isLoggable 设置成true
- 2、Log.isLoggable 分析
- (1)Log.java
- (2)android_util_Log.cpp
- (3)properties.cpp
- (4)logd_write.c
- 三、其他
- 1、Log.isLoggable(TAG, Log.DEBUG) 小结
- 设置Log.isLoggable 方法为true
- 设置Log.isLoggable的应用场景
- 2、Log.java 打印等级的值
- 3、Log.java 旧版本的代码
- 4、系统的Log.isLoggable(TAG, Log.DEBUG) 验证
- 5、在cmd窗口查看手机的Log日志相关命令
一、前言
Android系统代码或者系统应用代码中经常有一些Dug打印,是可以在调试模式中查看的;
比如:if(Log.isLoggable(TAG, Log.DEBUG)) {XXX}
或者定义 boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
后面有些打印使用这个DEBUG属性来决定是否打印某些日志。
之前我不太懂,也有看过源码,但是看到了native再后面就追不下去了,就没管了。
后面各种搜索和研究后又有了新的发现。
本文介绍一下如何查看这个默认关闭的DEBUG信息日志。
其实不难,就是prop属性的设置和读取;
但是估计要调试模式才能手动设置这个日志开关;
系统权限应用也可以设置这个属性进行控制。
二、答案和分析
1、Log.isLoggable 设置成true
因为分析的过程比较麻烦,所以直接先说答案了。
private static final String TAG = "WifiTracker";
private static final boolean DBG() {
return Log.isLoggable(TAG, Log.DEBUG);
}
//有些写成这样,比较简便:
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
如何让这个DBG() 方法或者 DEBUG 为true 呢?
下面两种方法:
1、在源码里面,把 Log.isLoggable(TAG, Log.DEBUG) 内容替换成 true,重新编译apk或者模块代码进行替换。
2、设置prop属性,log.tag.<TAG> 属性设置成D
比如上面这个类设置:setprop log.tag.WifiTracker D
重新加载这个类的时候,就会获取到 Log.isLoggable(TAG, Log.DEBUG) 为 true
第一种方法是最笨的方法,我之前经常是这样搞的。
第二种方法比较灵活,可以动态开启打印,关闭打印。
如果只要看Log.isLoggable的开启,那么到这里就可以了。
但是想看看系统代码是如何判断的可以往后看看。
Log.isLoggable开启后如何关闭?后面会提到!
2、Log.isLoggable 分析
下面分析的是Android14 的代码,不同系统版本可能有差异。
(1)Log.java
framework\base\core\java\android\util\Log.java
public final class Log {
...
/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
*
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>'
* Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, or ASSERT.
* You can also create a local.prop file that with the following in it:
* 'log.tag.<YOUR_LOG_TAG>=<LEVEL>'
* and place that in /data/local.prop.
*/
@FastNative
public static native boolean isLoggable(@Nullable String tag, @Level int level);
}
居然是native方法,那就就要看cpp代码了!
但是看上面注释的英文其实是有大致的说明了。
/**
*检查指定标记的日志是否可在指定级别记录。
*
*任何标签的默认级别都设置为INFO。这意味着,任何高于并包括以下级别的
*将记录信息。在调用日志方法之前,您应该检查以下内容
*如果你的标签应该被记录下来。您可以通过设置系统属性来更改默认级别:
*'setprop log.tag<;YOUR_LOG_TAG><;级别>'
*其中级别为VERBOSE、DEBUG、INFO、WARN、ERROR或ASSERT。//从小到大
*您还可以创建一个local.prop文件,其中包含以下内容:
*'log.tag<;YOUR_LOG_TAG>=<;级别>'
*并将其放置在/data/local.prop中。
*/
上面大致的意思:
1、可以设置prop属性 setprop log.tag.XXTAG 设置打印等级
TAG一般是当前类,也有可能是其他字符串,有些是整个应用使用某个TAG。
2、setprop log.tag<;YOUR_LOG_TAG ><;级别>
当你设置的打印属性等级 >= 定义的等级,日志就会记录,也就是返回true
这里定义了DEBUG(缩写D),如果prop属性设置成DEBUG(D)或者ERRORE(缩写成V),Java代码就会返回true
3、属性文件位置:/data/local.prop
这个估计是很旧的代码目录了,Android14 上没看到这个文件!
总的来说就是设置属性:setprop log.tag.XXTAG 设置成D就可以看到打印。
但是第2个>=就有点问题了:
实际代码中尝试prop属性设置成 "E" 是不会显示日志的,设置成 "V" 会显示日志;
难道是注释里面写错了,实际要prop属性的打印等级 <= level级别 ?
继续往下追代码看看!
(2)android_util_Log.cpp
framework\base\core\jni\android_util_Log.cpp
static jboolean isLoggable(const char* tag, jint level) {
return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}
这个就很难追下去了,__android_log_is_loggable 找不到实现?!
Android 源码framework、device下面搜了找不到 __android_log_is_loggable的实现,后面使用grep -nr 查看system目录下找到了!
(3)properties.cpp
system\logging\liblog\properties.cpp
int __android_log_is_loggable(int prio, const char* tag, int default_prio) {
auto len = tag ? strlen(tag) : 0;//tag字符串的长度
//四个参考:
//1、传入的等级,这里是 Log.DEBUG
//2、tag字符串
//3、tag的字符串长度
//4、默认等级,LOG_INFO,I
return __android_log_is_loggable_len(prio, tag, len, default_prio);
}
//具体实现判断是在这里:
int __android_log_is_loggable_len(int prio, const char* tag,
size_t len, int default_prio) {
//1、获取最小日志优先级和属性日志级别
int minimum_log_priority = __android_log_get_minimum_priority();
//2、这个级别通过系统属性prop配置的,过程在本类看起来很麻烦
int property_log_level = __android_log_level(tag, len);
if (property_log_level >= 0 && minimum_log_priority != ANDROID_LOG_DEFAULT) {
//3、判断传入的 Log.DEBUG 是否大于 prop或者minimum_log_priority 最小值
return prio >= std::min(property_log_level, minimum_log_priority);
} else if (property_log_level >= 0) {
//4、判断传入的 Log.DEBUG 是否大于 prop 值
return prio >= property_log_level;
} else if (minimum_log_priority != ANDROID_LOG_DEFAULT) {
return prio >= minimum_log_priority;
} else {
return prio >= default_prio;
}
}
上面第3点和第4点 都是有判断prop属性的值是否小于或者等于传入的等级值。
所以最终的实现是要prop定义的TAG值小于或者等于传入的Log.DEBUG 才会返回true。
所以对于isLoggable的示例判断:
Log.isLoggable(TAG, Log.DEBUG); 是否返回为true的判断是:
1、判断prop属性: log.tag.TAG 的 Log等级是否小于或者等于 Log.DEBUG;
2、Log的等级从小到大:VERBOSE、DEBUG、INFO、WARN、ERROR或ASSERT
3、所以 log.tag.TAG 等于 V 或者D 就会返回true
到这里简单代码就分析完了,很多复杂的过程在同级目录的其他文件,有兴趣的可以自己看看。
后面我从网上搜到的__android_log_is_loggable具体实现是在 logd_write.c;
但是我在Android13/14的代码上没有看到这个文件,发现是Android11 之前的代码才有;
(4)logd_write.c
但是Android9 和Android11 的代码 system/core/liblog/logd_write.c 文件中,以下是简化后的逻辑分析:
int __android_log_is_loggable(int prio, const char* tag, int default_log_level) {
if (prio >= ANDROID_LOG_WARN) {
return 1;
}
char value[PROP_VALUE_MAX];
if (tag != NULL) {
char key[PROP_NAME_MAX];
snprintf(key, sizeof(key), "log.tag.%s", tag);
if (__system_property_get(key, value) > 0) {
int prop_level = android_log_priority_to_int(value);
if (prop_level >= 0) {
//这里是判断出入的proi <= prop定义的等级,和最新的是相反的!
return prio <= prop_level;
} else if (strcmp(value, "SUPPRESS") == 0) {
return 0;
}
}
}
//最新的系统代码是去除 log.default 的判断的!
if (__system_property_get("log.default", value) > 0) {
int prop_level = android_log_priority_to_int(value);
if (prop_level >= 0) {
return prio <= prop_level;
}
}
return prio <= default_log_level;
}
从上面旧系统的代码逻辑看确实是判断 prop属性 log.tag. >= 传入的Log.DEBUG等级。
所以说旧系统代码和新系统代码逻辑是相反的;
Java代码里面的注释并没有进行修改!这个算是Google的一个小错误了。
三、其他
1、Log.isLoggable(TAG, Log.DEBUG) 小结
(1)Log.isLoggable默认都是返回false的
(2)Android 11 之前的版本设置prop属性 log.tag.TAG >= D 为true;
(3)Android 13 之后的版本设置prop属性 log.tag.TAG =< D 为true;
(4)不管什么系统版本设置成一样的等级最保险
(5)如果Android13 或者更新版本要关闭isLoggable的打印,设置大一点就行,比如E
(6)重启后设置的prop属性是不记忆的,要看打印要重新设置一次
设置Log.isLoggable 方法为true
串口设置:
setprop log.tag.XXTAG D //有些要这种V
系统/签名应用代码设置:
import android.os.SystemProperties;
SystemProperties.set("log.tag.XXXTAG", "V");
注意设置的属性,重启后是失效的,要重新设置。
如果prop属性是乱设置呢,比如 setprop log.tag.XXTAG XX
这个要看系统代码实现了,因为我试出了两种不同的结果:
Android14 的mtk方案,随便设置,isLoggable 是返回false的
Android13 的rk方案,随便设置,isLoggable 是返回true的
有兴趣的可以自己看看具体实现的cpp代码。
设置Log.isLoggable的应用场景
系统代码里面是有些 Log.isLoggable 的判断,可以用来查看某些默认隐藏的日志;
我们自己开发的系统应用也是可以这样操作:
普通的关键流程日志用Log.i正常打印,如果是比较多的数据,
比如wifi列表所有信息或者某个对象的具体信息,这种详细日志就用调试模式显示。
2、Log.java 打印等级的值
framework\base\core\java\android\util\Log.java
部分代码如下:
public final class Log {
/** @hide */
@IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE})
@Retention(RetentionPolicy.SOURCE)
public @interface Level {}
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
/**
* Priority constant for the println method.
*/
public static final int ASSERT = 7;
...
}
这里可以看到 VERBOSE, DEBUG, INFO, WARN, ERROR, or ASSERT.
打印等级是2到7。越大表示越严重,V是最小的2,A 是最大的7.
ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE 翻译过来分别对应:
断言、错误、警告、信息、调试、详细,
断言是可能导致系统崩溃的日志,错误可能导致应用崩溃的日志;
3、Log.java 旧版本的代码
网招的,不清楚是哪个安卓版本的,这个版本的prop判断逻辑都在一个Java代码里面
/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
* <p>
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>'
* Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
* turn off all logging for your tag. You can also create a local.prop file that with the
* following in it:
* 'log.tag.<YOUR_LOG_TAG>=<LEVEL>'
* and place that in /data/local.prop.
* </p>
*
* @param tag The tag to check.
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23.
*/
public static boolean isLoggable(String tag, int level) {
if (tag.length() > LOG_TAG_MAX_LENGTH) {
throw new IllegalArgumentException("Log tag " + tag + " exceeds limit of " + LOG_TAG_MAX_LENGTH + " characters");
}
// 如果级别大于等于 WARN ,直接返回 true
if (level >= Log.WARN) {
return true;
}
// 获取系统属性 log.tag.<TAG> 的值
String key = "log.tag." + tag;
String value = SystemProperties.get(key);
if (value != null) {
value = value.toUpperCase();
// 解析系统属性值对应的日志级别
int i = LogLevel.levelToInt(value, -1); //获取prop属性对应的调试等级int值
if (i >= 0) {
//这里也是prop属性的级别,大于或者等于输入的定义基本返回true
return level <= i;
} else if (value.equals("SUPPRESS")) {
return false;
}
}
// 如果没有设置系统属性,则检查全局的日志级别
value = SystemProperties.get("log.default");
if (value != null) {
value = value.toUpperCase();
int i = LogLevel.levelToInt(value, -1);
if (i >= 0) {
return level <= i;
}
}
return false;
}
上面的代码逻辑熟悉Java的大致可以看懂。
上面的Java代码逻辑大致和Android11 之前的系统Log等级判断是一致的。
不同系统的Log.isLoggable 后面具体逻辑有差异是正常的;
4、系统的Log.isLoggable(TAG, Log.DEBUG) 验证
Android系统中有很多这样的代码,比如热点开关打印的代码:
Settings\src\com\android\settings\network\TetherEnabler.java
public class TetherEnabler implements... {
private static final String TAG = "TetherEnabler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
void updateState(@Nullable String[] tethered) {
int state = getTetheringState(tethered);
if (DEBUG) {
Log.d(TAG, "updateState: " + state);//热点开关状态打印,0关闭,1开启
}
...
}
这个热点开关状态的打印是在:原生Settings一级界面刚进入二级界面"WLAN、热点"显示界面会执行updateState方法;
三级界面返回二级界面也是会执行updateState方法;
后续热点开关这里是没有执行updateState方法;
console:/ # logcat | grep TetherEnabler
//没日志!
console:/ # setprop log.tag.TetherEnabler D //设置prop属性
console:/ #
console:/ # logcat | grep TetherEnabler
03-18 20:12:40.290 1006 1006 D TetherEnabler: updateState: 0 //热点关闭状态
03-18 20:12:40.374 1006 1006 D TetherEnabler: updateState: 0
上面日志可以看到设置prop属性后就可以看到 DEBUG 的日志。
上面这个TetherEnabler 的打印是 Settings 的wifi服务正常启动才会进入 TetherEnabler的逻辑,否则不会调用 TetherEnabler 的方法。
有些类设置了prop属性,但是还是没有相关的打印日志,有可能是:
1、prop属性未设置正确
2、等级未设置正确
Log.isLoggable(TAG, Log.VERBOSE) 的等级,prop只能设置成V
3、代码逻辑确实不经过那里
5、在cmd窗口查看手机的Log日志相关命令
//格式1:打印默认日志数据
adb logcat
//格式2:需要打印日志详细时间的简单数据
adb logcat -v time
//格式3:需要打印级别为Error的信息
adb logcat *:E
//格式4:需要打印时间和级别是Error的信息
adb logcat -v time *:E
//格式5:将日志保存到电脑固定的位置,比如D:\log.txt
adb logcat -v time >D:\log.txt
https://blog.csdn.net/wenzhi20102321/article/details/81058196