当前位置: 首页 > article >正文

Android14 Log.isLoggable判断的分析

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.&lt;YOUR_LOG_TAG> &lt;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.&lt;YOUR_LOG_TAG>=&lt;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&lt;YOUR_LOG_TAG>&lt;级别>'
*其中级别为VERBOSE、DEBUG、INFO、WARN、ERROR或ASSERT。//从小到大
*您还可以创建一个local.prop文件,其中包含以下内容:
*'log.tag&lt;YOUR_LOG_TAG>=&lt;级别>'
*并将其放置在/data/local.prop中。
*/

上面大致的意思:

1、可以设置prop属性 setprop log.tag.XXTAG 设置打印等级
TAG一般是当前类,也有可能是其他字符串,有些是整个应用使用某个TAG。

2、setprop log.tag&lt;YOUR_LOG_TAG >&lt;级别>
当你设置的打印属性等级 >= 定义的等级,日志就会记录,也就是返回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.&lt;YOUR_LOG_TAG> &lt;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.&lt;YOUR_LOG_TAG>=&lt;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


http://www.kler.cn/a/594677.html

相关文章:

  • 《线程池最终版:使用可变参模板和future优化重构设计》
  • 【Azure 架构师学习笔记】- Azure Networking(1) -- Service Endpoint 和 Private Endpoint
  • JVM逃逸分析作用和原理
  • 大语言模型的训练数据清洗策略
  • Spring MVC 接口数据
  • 绿盟科技春招面试
  • 解决 FFmpeg 处理 H.264 视频时因分辨率对齐导致的崩溃问题
  • 20250320在荣品的PRO-RK3566开发板的buildroot系统下使用J27口的OTG0口接鼠标
  • AI+视频赋能智慧农业:EasyCVR打造全域可视化农场监管平台
  • Xcode16.1使用MonkeyDev运行Tiktok报错分析
  • Git(12)GitLab持续集成(CICD)
  • 在Qt中保存QComboBox变化前的值
  • 持续集成(CI)/持续部署(CD)
  • 【Unity Bug 随记】使用Rider debug功能时Unity Reload Domain卡死问题
  • sql-DDL
  • UDP协议原理
  • 【css酷炫效果】css酷炫效果100篇合集
  • MATLAB 调用arduino uno
  • 掌握些许 IPv6 要点,windows 远程桌面安全便利两相宜!
  • 【Redis】什么是缓存穿透、击穿、雪崩?如何解决?