【朝花夕拾】Android Log篇

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 笔者从事android开发有好几年了,经常会碰到这样的现象:由于不同的程序员对log输出有着不同的处理风格,同一款app中,往往有着五花八门的处理方式。 同时,对于Android Log的使用也一直存在不少模棱两可的认识:比如,log等级这么多,什么情况下要用什么等级;log哪些地方不宜使用,为什么不宜使用;Mobile log这么多,要如何高效地查看等。

前言        

       原文:【朝花夕拾】Android Log篇

       从事Android开发的这些年中,经常碰到这样一个现象:同一款app中,往往有好几种风格迥异的log处理方式,有时候会让维护者晕头转向。同时笔者也经常碰带一些模棱两可的问题:Log等级分好几种,到底什么情况下用哪个等级的log?什么情况下可以使用log,log怎么用,为什么要这么用?Android的log这么多,要怎么样高效地查看log?带着这些问题,笔者根据平时的开发经验、公司的log规范文档、网络中的相关资料,对log使用做了一定的整理。对于最基本的使用和log介绍,本文不做赘述,希望本文能帮助一部分人,也希望大牛们给出更牛的意见和建议,助我成长!

       本文主要内容如下:

         

 

 一、Log等级划分

    Android系统为开发者提供了良好的日志工具android.util.Log,常用的方法有如下5个,将log的输出等级也划分为了5个级别:

    1、Log.v:这里的v代表Verbose啰嗦的意思,对应的log等级为VERVOSE。采用该等级的log,任何消息都会输出。

    2、Log.d:这里的d代表Debug调试的意思,对应的log等级为DEBUG。采用该等级的log,除了VERBOSE级别的log外,剩余的4个等级的log都会被输出。

    3、Log.i:这里的i代表information,为一般提示性的消息,对应的log等级为INFO。采用该等级的log,不会输出VERBOSE和DEBUG信息,只会输出剩余3个等级的信息。

    4、Log.w:w代表warning警告信息,一般用于系统提示开发者需要优化android代码等场景,对应的等级为WARN。该级别log,只会输出WARN和ERROR的信息。

    5、Log.e:e代表error错误信息,一般用于输出异常和报错信息。该级别的log,只会输出该级别信息。一般Android系统在输出crassh等致命信息的时候,都会采用该级别的log。

 

二、Log使用规范

    不同的公司,对Log的使用有不同的要求和规范,以下笔者就工作中碰到的规范来举例说明Log的使用规范:

    1、在app中,一般不允许使用VERBOSE级别的log,对于INFO、WARN级别的log,允许极少量打印重要信息。这是工作中的要求,系统源码中其实对这三个等级用得也不少,例如,系统打印一般Exception信息时,就是用的WARN级别log

    2、只有在出现极严重错误的时候,才允许使用ERROR级别,一般的信息要是用DEBUG级别。当系统报Fatal Exception的时候,就是用的ERROR级别的log。

    3、用户的隐私信息禁止打印,比如:IMEI、手机号、密码、银行卡号等。在国外,一些法律也对Log内容做了严格的要求。

    4、Log中不要打印太多具体实现的细节,这样会导致通过log就能猜到架构的设计和代码的实现。

    5、Log中不能暴露核心算法或机制细节,比如核心算法相关信息、应用和框架间函数的调用流程等。

    6、禁止在循环打印log。在循环条件、频繁操作、频繁调用的接口、ACTION_MOVE事件、重复打印等地方,一定要控制好log的使用。在单位时间内,不同性质的应用对log的数目有一定的要求,对每条log的大小也有一定的限制。因为大量或者频繁的log,对app的性能有一定的影响。即便是有log开关控制日志的输出与否,字符串的拼接也是会耗掉一些性能和资源的。

    7、打印捕捉到的异常堆栈必须谨慎,如不需要打印堆栈就能定位问题,就尽量不要打印堆栈,若确实需要堆栈,在同一堆栈,尽量控制打印频度。

    8、对于Android源码中自带的log,尽量不要修改。在Event Log中,就严禁修改源码自带的log。

    9、Log中的TAG,一般以所划分的功能模块命名,log信息也最好用类名,方法名拼接为前缀。这样做的目的就是在查看log的时候,方便定位,对分析问题很有帮助。

   上述不仅包含使用规范,也包含了部分log使用小技巧。这些规范中有些会根据不同公司,不同严格程度而有所不同,而有些则需要统一遵守其规范的,读者可以根据具体情况斟酌。

   

三、Android Studio中查看log

        Android Studio为开发者提供了良好的log查看工具,开发者可以通过如下方式打开log视图:View > Tool Windows > Logcat,或者用默认的快捷键 Alt+6 打开/隐藏 Logcat视图。下面简单介绍一下该工具的使用。

    1、Logcat中选择筛选条件  

        如下截图中,标注了Android Studio中使用Logcat视图的常用功能,开发者可以根据实际情况选择过滤条件。

    2、Log信息颜色设置

        查看log的时候,有一个小技巧,为了便于查看不同等级的log,Android Studio对不同等级的log信息设置了不同的颜色。开发者也可以根据自己的爱好,自行设置颜色或者其他属性,这样,在查看log的时候,就容易对log等级进行区分,查看的时候就比较有层次感。设置路径为:File > Settings > Editor > Colors & Fonts > Android Logcat。如下截图所示:

              

        设置完成后,用如下代码进行测试


1  private void showLog(){
2         Log.v(TAG,"Hello,I am VERBOSE");
3         Log.d(TAG,"Hello,I am DEBUG");
4         Log.i(TAG,"Hello,I am INFORMATION");
5         Log.w(TAG,"Hello,I am WARNNING");
6         Log.e(TAG,"Hello,I am ERROR");
7     }

        logcat视图中打印的log信息如下:

             

        虽然开发者可以根据自己的爱好设置log的颜色等属性,但是笔者还是建议读者尽量遵守约定俗称的约定,比如,ERROR级别的log,就往往被设置为红色。

     3、Logcat中的log信息说明

        如下截图为笔者打印的某条log,对其中各个字段的进行了说明

        

 

四、写一份便于使用的Log辅助类

    Log的基本使用技能很容易掌握,但是要能灵活地使用在项目中,仍然有很多技巧需要掌握。

    1、开发者常碰到的场景

    在具体的开发中,开发者往往会遇到如下的情形:

    (1)调试的时候,往往会打印不少的log,用于辅助分析问题,但是要发布给用户使用的版本时,这些log必须要关闭掉。

    (2)开发者往往会在代码中设置一个变量,比如 boolean isDebug等,来控制日志的打印/关闭。但是每次发布版本的时候,都需要手动去修改这个值,操作不便,甚至容易忘记。

    (3)发布给用户使用的user版本,log被关闭了,出现bug需要分析的时候,log信息太少,往往又让开发者感到“巧妇难为无米之炊”,不利于分析问题。

    (4)拿到log信息后,又往往不容易找到这条信息和哪个功能有关,从哪个类,哪个方法中打印出来的。

    (5)有些log需要在user版本中关闭,但有些log需要一直保留,这两类log的处理,又需要区别对待。

    ······

    诸如此类的情形,想必开发者们都在不断地经历着。

    2、辅助工具类代码

        有经验的开发者一般都会写一个Log的辅助类来尽量规避这些麻烦,笔者在开发中也总结了一套代码,如下代码所示:


  1 package com.example.demos;
  2 
  3 import android.os.Build;
  4 import android.util.Log;
  5 
  6 public class Logger {
  7     private static final String TAG = "FunctionName";//功能模块名,比如你开发的是相机功能,这里可以命名为“Camera”,在查看log的时候,可以查看到该功能全部log
  8     private static final boolean isLogAnyTime = true;//任何情况下都允许打印的log,无论当前手机固件版本为“user”、“userdebug”还是“eng”模式
  9 
 10     /**
 11      * 用于根据是否允许打印log来决定是否打印DEBUG等级的log
 12      *
 13      * @param moduleTag  //输出该log处所在的类名
 14      * @param methodName //输出该log处所在的方法名
 15      * @param msg        //需要输出的信息
 16      */
 17     public static void d(String moduleTag, String methodName, String msg) {
 18         if (isTagLoggable(TAG, Log.DEBUG)) {
 19             Log.d(TAG, createLogPrefix(moduleTag, methodName, msg));
 20         }
 21     }
 22 
 23     /**
 24      * 在代码层面,任何情况下都会打印DEBUG等级的日志(在手机系统中也可以设置允许log打印的等级,这种情况另当别论)
 25      */
 26     public static void alwaysShowD(String moduleTag, String methodName, String msg) {
 27         Log.d(TAG, createLogPrefix(moduleTag, methodName, msg));
 28     }
 29 
 30     public static void e(String moduleTag, String methodName, String msg) {
 31         if (isTagLoggable(TAG, Log.ERROR)) {
 32             Log.e(TAG, createLogPrefix(moduleTag, methodName, msg));
 33         }
 34     }
 35 
 36     public static void alwaysShowE(String moduleTag, String methodName, String msg) {
 37         Log.e(TAG, createLogPrefix(moduleTag, methodName, msg));
 38     }
 39 
 40     /**
 41      * 用于打印方法的调用栈,即该函数一层一层的调用关系列表
 42      */
 43     public static void printStackTraceInfo() {
 44         if (isTagLoggable(TAG, Log.DEBUG)) {
 45             Log.d(TAG, Log.getStackTraceString(new Throwable()));
 46         }
 47     }
 48 
 49     /**
 50      * 获取捕捉到的Exception信息,并转化为字符串
 51      */
 52     public static void printExceptionInfo(Exception pEx) {
 53         String _exStr = pEx.toString() + "\n";
 54         StackTraceElement[] stackTraceElements = pEx.getStackTrace();
 55         if (stackTraceElements == null) {
 56             Log.w(TAG, _exStr);
 57         }
 58         for (StackTraceElement se : stackTraceElements) {
 59             _exStr += ("at " + se.getClassName() + "." + se.getMethodName() + "(" + se.getFileName() + ":" + se.getLineNumber() + ")\n");
 60         }
 61         Log.w(TAG, _exStr);
 62     }
 63 
 64     /**
 65      * 判断当前log是否允许输出
 66      * 
 67      * @param tag   官方:the tag to check
 68      * @param level 官方:the level to check
 69      * @return true 表示允许输出,false表示不允许输出
 70      */
 71     private static boolean isTagLoggable(String tag, int level) {
 72         return Log.isLoggable(tag, level) || isDebugMode() || isLogAnyTime;
 73     }
 74 
 75     /**
 76      * 将各个参数按照一定的格式组合,便于log查看
 77      *
 78      * @param moduleTag  传入所在的类名
 79      * @param methodName 传入所在的方法名
 80      * @param msg        要输出的信息
 81      * @return 组合后的字符串
 82      */
 83     private static String createLogPrefix(String moduleTag, String methodName, String msg) {
 84         StringBuffer buffer = new StringBuffer();
 85         buffer.append("[").append(moduleTag).append("]").append(methodName).append(":").append(msg);
 86         return buffer.toString();
 87     }
 88 
 89     /**
 90      * 手机的系统一般有“user”、“userdebug”、“eng”版本,“user”版本是最终发给用户使用的版本,
 91      * 而另外两种为工程师调试的版本,可以对手机做更多的操作,比如root,remount等。往往开发者
 92      * 用于调试的大部分log,在发给用户使用时(机user版本),必须要关闭掉。
 93      *
 94      * @return true 表示当前手机系统版本为“eng”或者“userdebug”版本
 95      * false表示“user”版本
 96      */
 97     private static boolean isDebugMode() {
 98         return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE);
 99     }
100 }


注:这套代码是根据公司的log使用规范来实现的,笔者当前从事手机系统app的开发,上述的处理办法也相对偏向这方面,但是对于纯第三方app开发者而言,也是可以参考的。

    3、辅助类的使用和说明。

    (1)打印基本log

         根据代码中的注释,想必对于这些方法的使用和含义,是很容易理解的。下面简单演示一下使用的例子

         在需要打印log的地方调用 Logger.d(className,methodName,msg);即可,如下演示了输出后的log

     

    (2)打印函数调用栈printStackTraceInfo

       以下截图展示了函数的调用栈,对于分析某个方法被调用的轨迹非常有用。第二行printStackTraceInfo()方法是最终捕捉调用栈的地方,可以清晰看到其调用轨迹。

       

    (3)打印异常信息printExceptionInfo(Exception pEx)

       该方法主要用打印捕获的Exception信息,如下截图一清晰展示地展示了异常原因,发生的地方,已经调用栈等信息。sdk也自带了e.printStackTrace()方法,由系统自己打印(截图二)。但是其打印信息被拆分为多条信息打印,在按某个tag进行搜索时,只能搜索到其中含有该tag的信息,而不能整体显示,自定义的方法就克服了这一点,便于整体查看。当然,读者可以根据自己爱好来选择是否用sdk自带的函数。

                 

                                                      截图一:自定义的异常打印

                 

                                                截图二:sdk自带的异常打印

    (4)使用Log.isLoggable(tagName, level)

       本小结中第1点第(3)条中有提到,调试版本中的log,在user版本中被关闭,这极大地妨碍了对bug的分析。所以在判断是否允许打印log的条件isTagLoggable(...)中,添加了一个“或”条件,Log.isLoggable(tag, level),就很好地解决了user版本中不能打印部分log的问题。加上这条件后,在user版本系统中,只要在命令框中执行如下命令即可:

adb shell setprop log.tag.tagName level

       命令中的tagName为辅助类中的TAG值,即FunctionName,level是指希望输出的log等级下限,比如,如果level为D,则除VERBOSE外,其他等级更高log都会输出;level为E,就只有ERROR等级log会输出。针对该辅助类的具体命令为:

adb shell setprop log.tag.FunctionName D

输入该命令后,凡是以“FunctionName”为tag名,等级在DEBUG及以上的log,就都会输出了。要想恢复到不可打印的状态,只要重启手机即可。

       推荐阅读:https://blog.csdn.net/qqxiaoqiang1573/article/details/72867776

 

五、log的获取

      设计好了log的输入策略,就可以获取log了。笔者接触到的获取log的方式主要有如下几种 

    1、开发工具中获取。

       比如上文中提到的Android Studio自带的Logcat视图,同样eclipse中也有该视图,都比较好用。这种方法主要被开发者使用,测试人员一般不会使用IDE中的类似工具。

    2、adb自带工具 logcat

       该命令功能也比较强大,使用起来非常方便,不需要额外的IDE,电脑上配置好adb,连接上手机,在命令框中输入命令即可。该工具的命令也不少,功能也比较强大,可惜,笔者对这个功能用得不多,主要使用IDE自带工具和手机的Mobile Log。

       推荐阅读:https://blog.csdn.net/liao277218962/article/details/50129009

    3、手机自带抓log功能

      一般手机也都自带了抓取log的工具,不同的品牌和机型,抓取系统log的方式和log的形式也不尽相同,下面以某比亚的某款机型为例来说明。

      (1)在拨号盘中输入暗码(可以在网上搜,不同品牌暗码各不同,同一手机中抓取log的种类也多样)就会进入到log工具界面,如下所示:

                     

              可以看到,可以抓取的log种类非常多,咱们这里只打开MobileLog。开发者可以根据实际情况选择开启需要的log,笔者目前为止,只用到过MoboleLog,-_-

      (2)在使用之前,先点击“清空”按钮清理掉之前的log文件, 以免无关log太多,影响查看有用信息。

      (3)点击“开始”按钮,系统就开始抓取log了。

      (4)开始操作手机,复现bug等,这段期间产生的log会被捕获到。

      (5)操作完成后,点击“关闭”按钮,系统会生成日志文件,在最底部可以看到日志的存储路径,在该路径下获取即可。

                     

 

六、查看及分析log

     拿到日志文件后,就可以分析log了。在IDE的视图工具Logcat中,和adb logcat中获取的log,基本的查看基本上都会,这里不多说了。这里主要讲讲MobileLog中log分析。进入到log文件夹后,会看到如下的文件夹列表

     

如果开启了MobileLog,重启手机或暂停后重新开启,均会产生一个最新的日志文件夹。开发者从bug复现最近的一次log开始分析。选择某个时间段日志文件夹后点击,会看到如下界面

    

一般咱们只关注moblie文件夹的内容(笔者目前为止也只使用过该目录下的文件)。点击进入后,会显示log文件列表,如下所示:

文件名中包含了机型、版本信息,以及文件中log的类型。一般咱们也只需要关注crash、main文件,有时候也会关注system日志文件。

  • crash文件中收集了系统中crash的log,首先分析这个文件,看是否有和自己项目相关的crash信息。
  • main文件,咱们前文中讲到的添加的log,允许打印的,都会被收集到该文件中。
  • system文件,收集系统的log,系统框架中自带的log会体现在该文件中,偶尔有需要使用。
  • 其他文件使用得不多,笔者暂时还没有碰到要使用剩余这几个文件的场景。

       在crash文件中,可以清晰地看到crash发生的时间,引起crash的进程及包名等信息。这里要注意crash的时间,如果和自己复现的场景时间差得比较远(比如10分钟以上),就可能和自己要分析的问题没太大的关联度。

     

        在main文件中,往往包含了大量的log信息。前面讲到的logcat视图或adb logcat捕获的log,以及不同机型手机中不同类型的log,其实基本结构基本相同。单条信息中也都包含了日期、时间、进程号、线程号、log等级、TAG,msg等信息。在分析这些log的时候,笔者这里提几个经常用的小技巧:

  • 选一个好用的文本编辑器。笔者和周围的同事基本上用的都是Notepad++,对查找信息非常有帮助,对于该工具的使用技巧,读者可以自己网上搜索一下。
  • 结合自己添加log的时候的设计,可以快速根据功能模块、类名、方法名等关键信息,筛选出关联度高的信息来。
  • 每一个app一般对应一个进程号,如果进程号中途变化了,说明中途该app发生了crash,可以在进程号变化点附近查找bug原因。

   笔者对MobileLog的分析技巧也在学习和摸索中,此处慢慢积累经验,慢慢总结,慢慢更新吧。

 

七、第三方工具

       当前在app开发生,也出现了不少比较优秀的管理log的第三方工具,笔者使用过的有两款:log4j和腾讯的bugly,都比较好用。

 

八、结语

        log的使用算是anroid开发中一个比较基础的技能了,也一个非常实用的技能,是开发中时时刻刻都要用到的技能。本文所讲的内容大多都算比较基础,当然也包含了一些平时容易忽视的知识点,基本上没有什么讲原理的地方。笔者在MobileLog分析等不少方面,经验也还比较浅,也在不断学习摸索中和总结中,希望读者们能多多指教,万分感谢!

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
监控 Android开发 C语言
深度解读Android崩溃日志案例分析2:tombstone日志
深度解读Android崩溃日志案例分析2:tombstone日志
504 0
|
26天前
|
存储 Java Android开发
Android|记一个导致 logback 无法输出日志的问题
在给一个 Android 项目添加 logback 日志框架时,遇到一个导致无法正常输出日志的问题,这里记录一下。
29 2
|
26天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
96 1
|
6月前
|
监控 安全 API
orhanobut/logger - 强大的Android日志打印库
orhanobut/logger - 强大的Android日志打印库
302 1
|
6月前
|
Android开发
Android 截屏 录屏 与获取log
Android 截屏 录屏 与获取log
56 1
|
6月前
|
Android开发
Android Mediatek 禁用 UART 日志输出
Android Mediatek 禁用 UART 日志输出
91 0
|
6月前
|
存储 缓存 监控
通过bugly日志分析,了解到的Android盲区知识
通过bugly日志分析,了解到的Android盲区知识
182 0
|
11月前
|
网络协议 Android开发 虚拟化
Android Studio无法运行程序调试程序出现Unable to connect to ADB.Check the Event Log for possible issues.Verify th
Android Studio无法运行程序调试程序出现Unable to connect to ADB.Check the Event Log for possible issues.Verify th
104 0
Android Studio无法运行程序调试程序出现Unable to connect to ADB.Check the Event Log for possible issues.Verify th
|
编译器 Android开发
深度解读Android崩溃日志案例分析1:so崩溃
深度解读Android崩溃日志案例分析1:so崩溃
324 1
|
15天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
131 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
下一篇
无影云桌面