1
很简单的话题。当我听到有人在讨论自己实现机制控制 log 输出时,我觉得还是有必要记录一下。最近让我比较困扰的是,很多 Android 基本的技巧都不被知晓。许多人的“锤子”意识很严重,一直使用以往的经验处理一切问题。

影响 Android log 输出的属性

Android 日志存储在由 logd 维护的一组化环形缓冲区,这组缓冲区包括:

  • main:用于存储大多数应用日志。
  • radio:用于存储通信相关的日志。
  • events:用于存储 Event 事件日志。
  • system:用于存储源自 Android 操作系统的消息。
  • crash:用于存储崩溃日志。
  • stats:用于存储系统状态事件日志。
  • security:用于存储安全相关事件日志。
  • kernel:用于存储 Linux 内核日志,其输出受其他几个属性的影响,详见 Logcat 读取 Kernel Log

Android 日志的每个条目都包含一个优先级、一个日志所属模块标记以及实际的日志消息。日志优先级代表日志输出的级别,其优先级为:VERBOSE(V) < DEBUG(D) < INFO(I) < WARNING(W) < ERROR(E) < FATAL(A) < SILENT(S)。日志系统根据日志级别来输出,仅输出当前级别及以下级别的日志。日志级别可以通过以下四个属性来控制,其优先级如下,

  • persist.log.tag.<MODULE_TAG>:模块日志级别,优先级高于系统日志级别,永久存储。
  • log.tag.<MODULE_TAG>:模块日志级别,优先级高于系统日志级别,临时存储,重启后消失。
  • persist.log.tag:系统日志级别,永久存储
  • log.tag:系统日志级别,临时存储,重启后消失。

persist.log.tag/log.tag 控制系统的日志级别,当模块日志级别没有设置时(默认都不会设置),所有日志都根据这个级别输出,其中persist.log.tag 优先级大于 log.tag

persist.log.tag.<MODULE_TAG>/log.tag.<MODULE_TAG> 控制模块的日志级别,但它的优先级大于系统日志级别。当模块日志级别存在时,改模块的日志输出仅由模块日志级别决定,不会参考系统日志级别。其中 persist.log.tag.<MODULE_TAG> 优先级大于 log.tag.<MODULE_TAG>

通过上述的四个属性,可以灵活的控制系统和模块的日志输出。通常,模块日志级别是不被设置的,但它对于开发调试是十分有用的。只要模块中含有足够的日志,就不需要重新编译模块,只要通过属性设置就可以得到需要的调试信息。当上述的四个属性都没有被设置时,系统会使用默认的日志级别,根据系统版本的不同,可能是 V 或 I。

如何使用 isLoggable

isLoggablelandroid.util.Log 提供的一个方法,它可以获取指定模块的日志级别。isLoggablel 提供了另一种控制日志输出的方法,可以通过指定模块的日志级别来控制当前日志输出。isLoggablel 的定义如下,

frameworks/base/core/java/android/util/Log.java
    
    /**
     * 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, 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.
     *
     * @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
     *         for Nougat (7.0) releases (API <= 23) and prior, there is no
     *         tag limit of concern after this API level.
     */
    public static native boolean isLoggable(String tag, int level);

isLoggable 在模块开发过程中十分有用,主要的使用场景为,

  • 根据当前模块的日志级别来控制模块的调试级别,根据调试级别可以控制代码流程和日志输出。
  • 获取相关模块的日志级别来控制当前模块的代码流程和日志输出。

isLoggable 比较定义的日志级别和参数中需要检验的级别。当 level 大于等于当前日志级别时,log 输出可以记录到日志中,函数返回 true。否则返回 false。一个典型的应用如下,

frameworks/base/core/java/android/bluetooth/BluetoothMapClient.java
    
public final class BluetoothMapClient implements BluetoothProfile {

    private static final String TAG = "BluetoothMapClient";
    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
    ......
    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
            new IBluetoothStateChangeCallback.Stub() {
                public void onBluetoothStateChange(boolean up) {
                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
                    if (!up) {
                        if (VDBG) Log.d(TAG, "Unbinding service...");
    ......

上述代码中,当 log.tag.BluetoothMapClient=D 时,DBG=true; VDBG=false 。这时 DBG 控制的相关 log 可以输出。当 log.tag.BluetoothMapClient=V 时,DBG=true; VDBG=trueDEBVDBG 控制的 log 都可以输出。

Native 日志输出控制

在 Android Native 中,通常使用 ALOGx() 系列函数来输出日志。 ALOGx() 函数与 Log.x() 系列函数一样,也受到日志级别的影响。同样,控制日志级别的四个属性对于 ALOGx() 系列函数也同样有效。这与 Java 层的日志输出并没有区别。ALOGx() 系列函数包括,

  • ALOGV(...):输出 VERBOSE 级别的日志。
  • ALOGD(...):输出 DEBUG 级别的日志。
  • ALOGI(...):输出 INFO 级别的日志。
  • ALOGW(...):输出 WARNING 级别的日志。
  • ALOGE(...):输出 ERROR 级别的日志。

除了使用 ALOGx() 输出日志外,还可以使用 ALOGx_IF() 系列函数来打印日志。ALOGx_IF() 函数根据输入的条件来决定是否打印日志,其函数包括,

  • ALOGV_IF(cond, ...)cond 为 true 时输出 VERBOSE 级别的日志。
  • ALOGD_IF(cond, ...)cond 为 true 时输出 DEBUG 级别的日志。
  • ALOGI_IF(cond, ...)cond 为 true 时输出 INFO 级别的日志。
  • ALOGW_IF(cond, ...)cond 为 true 时输出 WARNING 级别的日志。
  • ALOGE_IF(cond, ...)cond 为 true 时输出 ERROR 级别的日志。

此外,Native 中还有一个 IF_ALOGx() 系列函数,能够起到与 isLoggable 类似的功能,用于判断需求的级别是否大于当前日志级别,从而可以控制日志输出。IF_ALOGx() 系列函数包括,

  • IF_ALOGV():当日志级别小于等于 VERBOSE 时,返回 true。
  • IF_ALOGD():当日志级别小于等于 DEBUG 时,返回 true。
  • IF_ALOGI():当日志级别小于等于 INFO 时,返回 true。
  • IF_ALOGW():当日志级别小于等于 WARNING 时,返回 true。
  • IF_ALOGE():当日志级别小于等于 ERROR 时,返回 true。

最后

具体的代码就不撸了,Android 日志涉及的代码太多了,就不一点点看了,很多我也没看懂。在开发过程中能够做到合理使用日志系统就可以给我们带来很大的帮助。大部分情况,我们并不需要很清楚日志系统的实现,只需要做到充分的利用日志系统。切记摆脱“锤子”思维。

“手中有锤子的人,把世界上的一切都看成是钉子。” —— 查理·芒格


戈壁老王
143 声望64 粉丝

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,