【朝花夕拾】Android Log篇 您所在的位置:网站首页 安卓log查看器 【朝花夕拾】Android Log篇

【朝花夕拾】Android Log篇

2023-09-18 16:35| 来源: 网络整理| 查看: 265

前言        

        转载请声明,转自【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 *

Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e() 5 * methods. 6 * 7 *

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 *

Tip: A good convention is to declare a TAG constant 13 * in your class: 14 * 15 * private static final String TAG = "MyActivity"; 16 * 17 * and use that in subsequent calls to the log methods. 18 *

19 * 20 *

Tip: Don't forget that when you make a call like 21 * Log.v(TAG, "index=" + i); 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() + "\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 } 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.;YOUR_LOG_TAG> ;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.;YOUR_LOG_TAG>=;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 216 * for Nougat (7.0) releases (API


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有