【朝花夕拾】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() + "\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.&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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM