zoukankan      html  css  js  c++  java
  • 【朝花夕拾】Android Log篇

    前言        

            转载请声明,转自【https://www.cnblogs.com/andy-songwei/p/9676823.html】,谢谢!

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

           本文主要内容如下:

                        

     一、Log等级划分

       1、常用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。

        2、相关源码(基于android-26,下同)

         源码android.util.Log.java对Log的级别做了比较明确的说明,也依次给出了使用方法,相关源码片段如下所示:

     1 /**
     2  * API for sending log output.
     3  *
     4  * <p>Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e()
     5  * methods.
     6  *
     7  * <p>The order in terms of verbosity, from least to most is
     8  * ERROR, WARN, INFO, DEBUG, VERBOSE.  Verbose should never be compiled
     9  * into an application except during development.  Debug logs are compiled
    10  * in but stripped at runtime.  Error, warning and info logs are always kept.
    11  *
    12  * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
    13  * in your class:
    14  *
    15  * <pre>private static final String TAG = "MyActivity";</pre>
    16  *
    17  * and use that in subsequent calls to the log methods.
    18  * </p>
    19  *
    20  * <p><b>Tip:</b> Don't forget that when you make a call like
    21  * <pre>Log.v(TAG, "index=" + i);</pre>
    22  * that when you're building the string to pass into Log.d, the compiler uses a
    23  * StringBuilder and at least three allocations occur: the StringBuilder
    24  * itself, the buffer, and the String object.  Realistically, there is also
    25  * another buffer allocation and copy, and even more pressure on the gc.
    26  * That means that if your log message is filtered out, you might be doing
    27  * significant work and incurring significant overhead.
    28  */
    29 public final class Log {
    30 
    31     /**
    32      * Priority constant for the println method; use Log.v.
    33      */
    34     public static final int VERBOSE = 2;
    35 
    36     /**
    37      * Priority constant for the println method; use Log.d.
    38      */
    39     public static final int DEBUG = 3;
    40 
    41     /**
    42      * Priority constant for the println method; use Log.i.
    43      */
    44     public static final int INFO = 4;
    45 
    46     /**
    47      * Priority constant for the println method; use Log.w.
    48      */
    49     public static final int WARN = 5;
    50 
    51     /**
    52      * Priority constant for the println method; use Log.e.
    53      */
    54     public static final int ERROR = 6;
    55 
    56     /**
    57      * Priority constant for the println method.
    58      */
    59     public static final int ASSERT = 7;
    60 
    61      ......
    62
    View Code

       3、源码解读

         除了在注释中明确的解释说明外,我们也可以留意一下额外的信息

        (1)Log.java类被final所修饰,不能被继承,没有子类,同时它也没有继承别的类,没有父类。该类的逻辑关系比较简单,容易阅读,读者有机会可以阅读源码,一定会有更深入的理解。

        (2)可以看到,Log的输出等级还包括了ASSERT,用于输出的函数还包括Log.wtf(...)等,源码中也提到,一般只用上述五种级别log,对于ASSERT和Log.wtf()等就不多说,了解一下即可,平时开发也不必要使用。

        (3)Log的级别依次为2~7,有一个比较奇特的现象就是,没有0和1,不是从0或1开始排等级的,至于原因,读者如果感兴趣可以研究一下。

        (4)类名前的注释中也提到,传入log的字符串会耗用系统开销。所以咱们不能没有节制地使用Log,要讲究技巧和使用规范。

         ......

         更多的信息,读者可以多多发掘!

    二、Log使用规范

        不同的公司,对Log的使用有不同的要求和规范,以下笔者就工作中碰到的规范来举例说明Log的使用规范(当然,从上节中的源码注释中,也能看出一些端倪来):

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

        2、只有在出现极严重错误的时候,才允许使用ERROR级别,一般的信息要是用DEBUG级别(在后面讲Log.isLoggable()的时候,会讲到用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() + "
    ";
     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() + ")
    ");
     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 }
    View Code

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

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

        (1)打印基本log

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

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

         

            这里提一个小技巧:对于className的获取,可以采用如下的方法(这里的TAG,就是传入的Logger.d(...)中的类名了):

    public class HandleDemoActivity extends AppCompatActivity {
        private static final String TAG = HandleDemoActivity.class.getSimpleName();
        ......
    }

    类名.class.getSimpleName()返回的结果就是"HandleDemoActivity",这样做最大的好处就是,如果类名有变化,这个值也会随着改变,如果采用硬编码写死这个变量,灵活性会比较差。

        (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的问题。

            1)基本使用

           加上这条件后,在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,就都会输出了。要想恢复到不可打印的状态,只要重启手机即可。

          2)相关源码

     1 /**
     2      * Checks to see whether or not a log for the specified tag is loggable at the specified level.
     3      *
     4      *  The default level of any tag is set to INFO. This means that any level above and including
     5      *  INFO will be logged. Before you make any calls to a logging method you should check to see
     6      *  if your tag should be logged. You can change the default level by setting a system property:
     7      *      'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
     8      *  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
     9      *  turn off all logging for your tag. You can also create a local.prop file that with the
    10      *  following in it:
    11      *      'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
    12      *  and place that in /data/local.prop.
    13      *
    14      * @param tag The tag to check.
    15      * @param level The level to check.
    16      * @return Whether or not that this is allowed to be logged.
    17      * @throws IllegalArgumentException is thrown if the tag.length() > 23
    18      *         for Nougat (7.0) releases (API <= 23) and prior, there is no
    19      *         tag limit of concern after this API level.
    20      */
    21     public static native boolean isLoggable(String tag, int level);
    View Code

         3)源码解读

           依据以上源码及注释,笔者提取了部分信息:

    • 该方法是一个native方法,通过jni在底层实现。
    • 在没有设置运行打印的tag的等级的时候,默认等级为INFO,即此时isLoggable(tag,level)中的leve如果为VERBOSE或DEBUG,isLoggable()会返回false,其它等级level则会返回true。
    • 此处可以设置的level等级更多,在前面提到的5个等级之后,还有ASSERT、SUPPRESS,设置为SUPPRESS可以关闭所有log。
    • TAG字符串长度不可太长,超过23个后,后面的会被截断。
    • 注释中也提供了修改系统文件的方法,来设置允许打印的log的等级。

         4)测试代码

           如下为一个测试函数

     1 private void testIsLoggable() {
     2         boolean b1 = Log.isLoggable(TAG, Log.VERBOSE);
     3         boolean b2 = Log.isLoggable(TAG, Log.DEBUG);
     4         boolean b3 = Log.isLoggable(TAG, Log.INFO);
     5         boolean b4 = Log.isLoggable(TAG, Log.WARN);
     6         boolean b5 = Log.isLoggable(TAG, Log.ERROR);
     7         Log.e(TAG, "" + b1 + ";" + b2 + ";" + b3 + ";" + b4 + ";" + b5);
     8         Log.v(TAG,"VERBOSE log can be print");
     9         Log.d(TAG,"DEBUG log can be print");
    10         Log.i(TAG,"INFO log can be print");
    11         Log.w(TAG,"WARN log can be print");
    12         Log.e(TAG,"ERROR log can be print");
    13     }

         5)测试结果

            a)不执行任何命令,测试结果为:

          

           证明了tag默认level为INFO的结论,但是Log.v() - Log.e() 均能打印出log。

             b)执行命令

    adb shell setprop log.tag.HandleDemoActivity I

            测试结果为:

             

              不知道读者有没有发现,尽管默认level为INFO,此处命令设置的值也为INFO,但此时Log.v()和Log.d()的msg都没有再输出了。

             c) 执行命令

    adb shell setprop log.tag.HandleDemoActivity W

           测试结果为:

           

              d)结论

           这里咱们也可以看到,Log.isLoggable(TAG,Log.DEBUG)的值默认是false。咱们在第二节讲Log的使用规范时的第2点中提到过,一般信息的打印用DEBUG级别log,再结合前面给出的Log辅助类,在这里可以感受到一些好处了,当然读者也可以根据自己的理解,利用这些条件设计自己得心应手的使用方法来。

           以上的测试结果,我们还可以得到一个结论:adb shell setprop log.tag.tagName level 不仅会改变Log.isLoggable(tag,level)的返回值,也会影响到Log.v() - Log.e() 是否打印。读者一定要注意这些细微的差别,笔者刚开始的时候,也忽视过,也曾蒙圈过-_-!

           6)推荐阅读

             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分析。

     1、文档结构

          进入到log文件夹后,会看到如下的文件夹列表

         

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

         

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

       

      2、分析log文件

        (1)log文件概要

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

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

        (2)分析crash文件log

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

         

        (3)分析main文件log

            在main文件中,往往包含了大量的log信息。前面讲到的logcat视图或adb logcat捕获的log,以及不同机型手机中不同类型的log,其实基本结构基本相同。单条信息中也都包含了日期、时间、进程号、线程号、log等级、TAG,msg等信息。如下图所示:

            

          在分析这些log的时候,笔者这里提几个经常用的小技巧:

    • 选一个好用的文本编辑器。笔者和周围的同事基本上用的都是Notepad++,对查找信息非常有帮助,对于该工具的使用技巧,读者可以自己网上搜索一下。
    • 结合自己添加log的时候的设计,可以快速根据功能模块、类名、方法名等关键信息,筛选出关联度高的信息来。
    • 每一个app一般对应一个进程号,如果进程号中途变化了,说明中途该app发生了crash,可以在进程号变化点附近查找bug原因。
    • 最重要一点,对这类的log,就是要大量看。有时候看一遍可能找不出问题,就要反复看,找各种关键字搜索,必要时,甚至要逐行逐行看。就像要提升写作技能,首先必须大量去写一样,这是不二法门。

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

      3、源码解析

          在源码中有如下的代码

     1     public static int v(String tag, String msg) {
     2         return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
     3     }
     4     ......
     5     public static int d(String tag, String msg) {
     6         return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
     7     }
     8     ......
     9      public static int i(String tag, String msg) {
    10         return println_native(LOG_ID_MAIN, INFO, tag, msg);
    11     }
    12     ......
    13       public static int w(String tag, String msg) {
    14         return println_native(LOG_ID_MAIN, WARN, tag, msg);
    15     }
    16     ......
    17     public static int e(String tag, String msg) {
    18         return println_native(LOG_ID_MAIN, ERROR, tag, msg);
    19     }
    20     ......
    21     /** @hide */ public static final int LOG_ID_MAIN = 0;
    22     /** @hide */ public static final int LOG_ID_RADIO = 1;
    23     /** @hide */ public static final int LOG_ID_EVENTS = 2;
    24     /** @hide */ public static final int LOG_ID_SYSTEM = 3;
    25     /** @hide */ public static final int LOG_ID_CRASH = 4;
    26 
    27     /** @hide */ public static native int println_native(int bufID,
    28             int priority, String tag, String msg);
    View Code

           源码中也正好给出了LOG_ID_MAIN ~ LOG_ID_CRASH 5个LOG_ID值,除了event log外,其它的也是一一对应。Log.v()~Log.e()方法的实现都调用了println_native()方法,传入其中的第一个参数bufID值也都是LOG_ID_MAIN,正好这些方法输出的log都保存在了main文件中。笔者尚未找到明确的资料来证明这其中的联系,但笔者认为,应该不是巧合,读者有兴趣可以自己再深入研究研究。另外,我们也可以发现,println_native()也是个native方法,通过jni在本地实现。

    七、第三方工具

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

           个人建议:使用第三方工具,就必然要导入第三方的jar包,sdk等,无疑会增加app的负载。一般来说,如果自己写的log辅助类能够轻松实现想要的需求,能不用还是别用吧。当然,个人经验来看,bugly这类功能不太容易自己实现 -_-

    八、结语

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

    附录

         在文章的最后附上android.util.Log.java的源码,有需要的,可以点开研读研读,在此中秋佳节来临之际,也祝愿所有读者中秋快乐。

      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.util;
     18 
     19 import android.os.DeadSystemException;
     20 
     21 import com.android.internal.os.RuntimeInit;
     22 import com.android.internal.util.FastPrintWriter;
     23 import com.android.internal.util.LineBreakBufferedWriter;
     24 
     25 import java.io.PrintWriter;
     26 import java.io.StringWriter;
     27 import java.io.Writer;
     28 import java.net.UnknownHostException;
     29 
     30 /**
     31  * API for sending log output.
     32  *
     33  * <p>Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e()
     34  * methods.
     35  *
     36  * <p>The order in terms of verbosity, from least to most is
     37  * ERROR, WARN, INFO, DEBUG, VERBOSE.  Verbose should never be compiled
     38  * into an application except during development.  Debug logs are compiled
     39  * in but stripped at runtime.  Error, warning and info logs are always kept.
     40  *
     41  * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
     42  * in your class:
     43  *
     44  * <pre>private static final String TAG = "MyActivity";</pre>
     45  *
     46  * and use that in subsequent calls to the log methods.
     47  * </p>
     48  *
     49  * <p><b>Tip:</b> Don't forget that when you make a call like
     50  * <pre>Log.v(TAG, "index=" + i);</pre>
     51  * that when you're building the string to pass into Log.d, the compiler uses a
     52  * StringBuilder and at least three allocations occur: the StringBuilder
     53  * itself, the buffer, and the String object.  Realistically, there is also
     54  * another buffer allocation and copy, and even more pressure on the gc.
     55  * That means that if your log message is filtered out, you might be doing
     56  * significant work and incurring significant overhead.
     57  */
     58 public final class Log {
     59 
     60     /**
     61      * Priority constant for the println method; use Log.v.
     62      */
     63     public static final int VERBOSE = 2;
     64 
     65     /**
     66      * Priority constant for the println method; use Log.d.
     67      */
     68     public static final int DEBUG = 3;
     69 
     70     /**
     71      * Priority constant for the println method; use Log.i.
     72      */
     73     public static final int INFO = 4;
     74 
     75     /**
     76      * Priority constant for the println method; use Log.w.
     77      */
     78     public static final int WARN = 5;
     79 
     80     /**
     81      * Priority constant for the println method; use Log.e.
     82      */
     83     public static final int ERROR = 6;
     84 
     85     /**
     86      * Priority constant for the println method.
     87      */
     88     public static final int ASSERT = 7;
     89 
     90     /**
     91      * Exception class used to capture a stack trace in {@link #wtf}.
     92      */
     93     private static class TerribleFailure extends Exception {
     94         TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
     95     }
     96 
     97     /**
     98      * Interface to handle terrible failures from {@link #wtf}.
     99      *
    100      * @hide
    101      */
    102     public interface TerribleFailureHandler {
    103         void onTerribleFailure(String tag, TerribleFailure what, boolean system);
    104     }
    105 
    106     private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
    107             public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
    108                 RuntimeInit.wtf(tag, what, system);
    109             }
    110         };
    111 
    112     private Log() {
    113     }
    114 
    115     /**
    116      * Send a {@link #VERBOSE} log message.
    117      * @param tag Used to identify the source of a log message.  It usually identifies
    118      *        the class or activity where the log call occurs.
    119      * @param msg The message you would like logged.
    120      */
    121     public static int v(String tag, String msg) {
    122         return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
    123     }
    124 
    125     /**
    126      * Send a {@link #VERBOSE} log message and log the exception.
    127      * @param tag Used to identify the source of a log message.  It usually identifies
    128      *        the class or activity where the log call occurs.
    129      * @param msg The message you would like logged.
    130      * @param tr An exception to log
    131      */
    132     public static int v(String tag, String msg, Throwable tr) {
    133         return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
    134     }
    135 
    136     /**
    137      * Send a {@link #DEBUG} log message.
    138      * @param tag Used to identify the source of a log message.  It usually identifies
    139      *        the class or activity where the log call occurs.
    140      * @param msg The message you would like logged.
    141      */
    142     public static int d(String tag, String msg) {
    143         return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    144     }
    145 
    146     /**
    147      * Send a {@link #DEBUG} log message and log the exception.
    148      * @param tag Used to identify the source of a log message.  It usually identifies
    149      *        the class or activity where the log call occurs.
    150      * @param msg The message you would like logged.
    151      * @param tr An exception to log
    152      */
    153     public static int d(String tag, String msg, Throwable tr) {
    154         return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
    155     }
    156 
    157     /**
    158      * Send an {@link #INFO} log message.
    159      * @param tag Used to identify the source of a log message.  It usually identifies
    160      *        the class or activity where the log call occurs.
    161      * @param msg The message you would like logged.
    162      */
    163     public static int i(String tag, String msg) {
    164         return println_native(LOG_ID_MAIN, INFO, tag, msg);
    165     }
    166 
    167     /**
    168      * Send a {@link #INFO} log message and log the exception.
    169      * @param tag Used to identify the source of a log message.  It usually identifies
    170      *        the class or activity where the log call occurs.
    171      * @param msg The message you would like logged.
    172      * @param tr An exception to log
    173      */
    174     public static int i(String tag, String msg, Throwable tr) {
    175         return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
    176     }
    177 
    178     /**
    179      * Send a {@link #WARN} log message.
    180      * @param tag Used to identify the source of a log message.  It usually identifies
    181      *        the class or activity where the log call occurs.
    182      * @param msg The message you would like logged.
    183      */
    184     public static int w(String tag, String msg) {
    185         return println_native(LOG_ID_MAIN, WARN, tag, msg);
    186     }
    187 
    188     /**
    189      * Send a {@link #WARN} log message and log the exception.
    190      * @param tag Used to identify the source of a log message.  It usually identifies
    191      *        the class or activity where the log call occurs.
    192      * @param msg The message you would like logged.
    193      * @param tr An exception to log
    194      */
    195     public static int w(String tag, String msg, Throwable tr) {
    196         return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
    197     }
    198 
    199     /**
    200      * Checks to see whether or not a log for the specified tag is loggable at the specified level.
    201      *
    202      *  The default level of any tag is set to INFO. This means that any level above and including
    203      *  INFO will be logged. Before you make any calls to a logging method you should check to see
    204      *  if your tag should be logged. You can change the default level by setting a system property:
    205      *      'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
    206      *  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
    207      *  turn off all logging for your tag. You can also create a local.prop file that with the
    208      *  following in it:
    209      *      'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
    210      *  and place that in /data/local.prop.
    211      *
    212      * @param tag The tag to check.
    213      * @param level The level to check.
    214      * @return Whether or not that this is allowed to be logged.
    215      * @throws IllegalArgumentException is thrown if the tag.length() > 23
    216      *         for Nougat (7.0) releases (API <= 23) and prior, there is no
    217      *         tag limit of concern after this API level.
    218      */
    219     public static native boolean isLoggable(String tag, int level);
    220 
    221     /*
    222      * Send a {@link #WARN} log message and log the exception.
    223      * @param tag Used to identify the source of a log message.  It usually identifies
    224      *        the class or activity where the log call occurs.
    225      * @param tr An exception to log
    226      */
    227     public static int w(String tag, Throwable tr) {
    228         return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
    229     }
    230 
    231     /**
    232      * Send an {@link #ERROR} log message.
    233      * @param tag Used to identify the source of a log message.  It usually identifies
    234      *        the class or activity where the log call occurs.
    235      * @param msg The message you would like logged.
    236      */
    237     public static int e(String tag, String msg) {
    238         return println_native(LOG_ID_MAIN, ERROR, tag, msg);
    239     }
    240 
    241     /**
    242      * Send a {@link #ERROR} log message and log the exception.
    243      * @param tag Used to identify the source of a log message.  It usually identifies
    244      *        the class or activity where the log call occurs.
    245      * @param msg The message you would like logged.
    246      * @param tr An exception to log
    247      */
    248     public static int e(String tag, String msg, Throwable tr) {
    249         return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
    250     }
    251 
    252     /**
    253      * What a Terrible Failure: Report a condition that should never happen.
    254      * The error will always be logged at level ASSERT with the call stack.
    255      * Depending on system configuration, a report may be added to the
    256      * {@link android.os.DropBoxManager} and/or the process may be terminated
    257      * immediately with an error dialog.
    258      * @param tag Used to identify the source of a log message.
    259      * @param msg The message you would like logged.
    260      */
    261     public static int wtf(String tag, String msg) {
    262         return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
    263     }
    264 
    265     /**
    266      * Like {@link #wtf(String, String)}, but also writes to the log the full
    267      * call stack.
    268      * @hide
    269      */
    270     public static int wtfStack(String tag, String msg) {
    271         return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
    272     }
    273 
    274     /**
    275      * What a Terrible Failure: Report an exception that should never happen.
    276      * Similar to {@link #wtf(String, String)}, with an exception to log.
    277      * @param tag Used to identify the source of a log message.
    278      * @param tr An exception to log.
    279      */
    280     public static int wtf(String tag, Throwable tr) {
    281         return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
    282     }
    283 
    284     /**
    285      * What a Terrible Failure: Report an exception that should never happen.
    286      * Similar to {@link #wtf(String, Throwable)}, with a message as well.
    287      * @param tag Used to identify the source of a log message.
    288      * @param msg The message you would like logged.
    289      * @param tr An exception to log.  May be null.
    290      */
    291     public static int wtf(String tag, String msg, Throwable tr) {
    292         return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
    293     }
    294 
    295     static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
    296             boolean system) {
    297         TerribleFailure what = new TerribleFailure(msg, tr);
    298         // Only mark this as ERROR, do not use ASSERT since that should be
    299         // reserved for cases where the system is guaranteed to abort.
    300         // The onTerribleFailure call does not always cause a crash.
    301         int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
    302         sWtfHandler.onTerribleFailure(tag, what, system);
    303         return bytes;
    304     }
    305 
    306     static void wtfQuiet(int logId, String tag, String msg, boolean system) {
    307         TerribleFailure what = new TerribleFailure(msg, null);
    308         sWtfHandler.onTerribleFailure(tag, what, system);
    309     }
    310 
    311     /**
    312      * Sets the terrible failure handler, for testing.
    313      *
    314      * @return the old handler
    315      *
    316      * @hide
    317      */
    318     public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
    319         if (handler == null) {
    320             throw new NullPointerException("handler == null");
    321         }
    322         TerribleFailureHandler oldHandler = sWtfHandler;
    323         sWtfHandler = handler;
    324         return oldHandler;
    325     }
    326 
    327     /**
    328      * Handy function to get a loggable stack trace from a Throwable
    329      * @param tr An exception to log
    330      */
    331     public static String getStackTraceString(Throwable tr) {
    332         if (tr == null) {
    333             return "";
    334         }
    335 
    336         // This is to reduce the amount of log spew that apps do in the non-error
    337         // condition of the network being unavailable.
    338         Throwable t = tr;
    339         while (t != null) {
    340             if (t instanceof UnknownHostException) {
    341                 return "";
    342             }
    343             t = t.getCause();
    344         }
    345 
    346         StringWriter sw = new StringWriter();
    347         PrintWriter pw = new FastPrintWriter(sw, false, 256);
    348         tr.printStackTrace(pw);
    349         pw.flush();
    350         return sw.toString();
    351     }
    352 
    353     /**
    354      * Low-level logging call.
    355      * @param priority The priority/type of this log message
    356      * @param tag Used to identify the source of a log message.  It usually identifies
    357      *        the class or activity where the log call occurs.
    358      * @param msg The message you would like logged.
    359      * @return The number of bytes written.
    360      */
    361     public static int println(int priority, String tag, String msg) {
    362         return println_native(LOG_ID_MAIN, priority, tag, msg);
    363     }
    364 
    365     /** @hide */ public static final int LOG_ID_MAIN = 0;
    366     /** @hide */ public static final int LOG_ID_RADIO = 1;
    367     /** @hide */ public static final int LOG_ID_EVENTS = 2;
    368     /** @hide */ public static final int LOG_ID_SYSTEM = 3;
    369     /** @hide */ public static final int LOG_ID_CRASH = 4;
    370 
    371     /** @hide */ public static native int println_native(int bufID,
    372             int priority, String tag, String msg);
    373 
    374     /**
    375      * Return the maximum payload the log daemon accepts without truncation.
    376      * @return LOGGER_ENTRY_MAX_PAYLOAD.
    377      */
    378     private static native int logger_entry_max_payload_native();
    379 
    380     /**
    381      * Helper function for long messages. Uses the LineBreakBufferedWriter to break
    382      * up long messages and stacktraces along newlines, but tries to write in large
    383      * chunks. This is to avoid truncation.
    384      * @hide
    385      */
    386     public static int printlns(int bufID, int priority, String tag, String msg,
    387             Throwable tr) {
    388         ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
    389         // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
    390         // and the length of the tag.
    391         // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
    392         //       is too expensive to compute that ahead of time.
    393         int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD  // Base.
    394                 - 2                                                // Two terminators.
    395                 - (tag != null ? tag.length() : 0)                 // Tag length.
    396                 - 32;                                              // Some slack.
    397         // At least assume you can print *some* characters (tag is not too large).
    398         bufferSize = Math.max(bufferSize, 100);
    399 
    400         LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
    401 
    402         lbbw.println(msg);
    403 
    404         if (tr != null) {
    405             // This is to reduce the amount of log spew that apps do in the non-error
    406             // condition of the network being unavailable.
    407             Throwable t = tr;
    408             while (t != null) {
    409                 if (t instanceof UnknownHostException) {
    410                     break;
    411                 }
    412                 if (t instanceof DeadSystemException) {
    413                     lbbw.println("DeadSystemException: The system died; "
    414                             + "earlier logs will point to the root cause");
    415                     break;
    416                 }
    417                 t = t.getCause();
    418             }
    419             if (t == null) {
    420                 tr.printStackTrace(lbbw);
    421             }
    422         }
    423 
    424         lbbw.flush();
    425 
    426         return logWriter.getWritten();
    427     }
    428 
    429     /**
    430      * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
    431      * a JNI call during logging.
    432      */
    433     static class NoPreloadHolder {
    434         public final static int LOGGER_ENTRY_MAX_PAYLOAD =
    435                 logger_entry_max_payload_native();
    436     }
    437 
    438     /**
    439      * Helper class to write to the logcat. Different from LogWriter, this writes
    440      * the whole given buffer and does not break along newlines.
    441      */
    442     private static class ImmediateLogWriter extends Writer {
    443 
    444         private int bufID;
    445         private int priority;
    446         private String tag;
    447 
    448         private int written = 0;
    449 
    450         /**
    451          * Create a writer that immediately writes to the log, using the given
    452          * parameters.
    453          */
    454         public ImmediateLogWriter(int bufID, int priority, String tag) {
    455             this.bufID = bufID;
    456             this.priority = priority;
    457             this.tag = tag;
    458         }
    459 
    460         public int getWritten() {
    461             return written;
    462         }
    463 
    464         @Override
    465         public void write(char[] cbuf, int off, int len) {
    466             // Note: using String here has a bit of overhead as a Java object is created,
    467             //       but using the char[] directly is not easier, as it needs to be translated
    468             //       to a C char[] for logging.
    469             written += println_native(bufID, priority, tag, new String(cbuf, off, len));
    470         }
    471 
    472         @Override
    473         public void flush() {
    474             // Ignored.
    475         }
    476 
    477         @Override
    478         public void close() {
    479             // Ignored.
    480         }
    481     }
    482 }
    View Code

      

  • 相关阅读:
    PYTHON 爬虫笔记八:利用Requests+正则表达式爬取猫眼电影top100(实战项目一)
    PYTHON 爬虫笔记七:Selenium库基础用法
    PYTHON 爬虫笔记六:PyQuery库基础用法
    java练习题(六)
    js练习题(三)
    js练习题(一)
    表格
    MAYA网络Z1J10928
    //绑定select下拉框change事件只执行一次
    枚举数组转换字符串
  • 原文地址:https://www.cnblogs.com/andy-songwei/p/9676823.html
Copyright © 2011-2022 走看看