【朝花夕拾】Android Log篇

前言        html

        轉載請聲明,轉自【http://www.javashuo.com/article/p-tygjefwy-g.html】,謝謝!java

        從事Android開發的這些年中,常常碰到這樣一個現象:同一款app中,每每有好幾種風格迥異的log處理方式,有時候會讓維護者暈頭轉向。同時筆者也常常碰帶一些模棱兩可的問題:Log等級分好幾種,到底什麼狀況下用哪一個等級的log?什麼狀況下可使用log,log怎麼用,爲何要這麼用?Android的log這麼多,要怎麼樣高效地查看log?帶着這些問題,筆者根據平時的開發經驗、公司的log規範文檔、網絡中的相關資料,對log使用作了必定的整理。對於最基本的使用和log介紹,本文不作贅述,但願本文能幫助一部分人,也但願大牛們給出更牛的意見和建議,助我成長!android

       本文主要內容以下:算法

                    

 

 1、Log等級劃分shell

   一、經常使用Log等級express

    Android系統爲開發者提供了良好的日誌工具android.util.Log,經常使用的方法有以下5個,將log的輸出等級也依次指定了5個級別:apache

    (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個等級的信息。app

    (4)Log.w:w表明warning警告信息,通常用於系統提示開發者須要優化android代碼等場景,對應的等級爲WARN。該級別log,只會輸出WARN和ERROR的信息。

    (5)Log.e:e表明error錯誤信息,通常用於輸出異常和報錯信息。該級別的log,只會輸出該級別信息。通常Android系統在輸出crassh等致命信息的時候,都會採用該級別的log。

    二、相關源碼(基於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

   三、源碼解讀

     除了在註釋中明確的解釋說明外,咱們也能夠留意一下額外的信息

    (1)Log.java類被final所修飾,不能被繼承,沒有子類,同時它也沒有繼承別的類,沒有父類。該類的邏輯關係比較簡單,容易閱讀,讀者有機會能夠閱讀源碼,必定會有更深刻的理解。

    (2)能夠看到,Log的輸出等級還包括了ASSERT,用於輸出的函數還包括Log.wtf(...)等,源碼中也提到,通常只用上述五種級別log,對於ASSERT和Log.wtf()等就很少說,瞭解一下便可,平時開發也沒必要要使用。

    (3)Log的級別依次爲2~7,有一個比較奇特的現象就是,沒有0和1,不是從0或1開始排等級的,至於緣由,讀者若是感興趣能夠研究一下。

    (4)類名前的註釋中也提到,傳入log的字符串會耗用系統開銷。因此我們不能沒有節制地使用Log,要講究技巧和使用規範。

     ......

     更多的信息,讀者能夠多多發掘!

2、Log使用規範

    不一樣的公司,對Log的使用有不一樣的要求和規範,如下筆者就工做中碰到的規範來舉例說明Log的使用規範(固然,從上節中的源碼註釋中,也能看出一些端倪來):

    一、在app中,通常不容許使用VERBOSE級別的log,對於INFO、WARN級別的log,容許極少許打印重要信息。這是工做中的要求,系統源碼中其實對這三個等級用得也很多,例如,系統打印通常Exception信息時,就是用的WARN級別log

    二、只有在出現極嚴重錯誤的時候,才容許使用ERROR級別,通常的信息要是用DEBUG級別(在後面講Log.isLoggable()的時候,會講到用DEBUG級別的好處)。當系統報Fatal Exception的時候,就是用的ERROR級別的log。

    三、用戶的隱私信息禁止打印,好比:IMEI、手機號、密碼、銀行卡號等。在國外,一些法律也對Log內容作了嚴格的要求。

    四、Log中不要打印太多具體實現的細節,這樣會致使經過log就能猜到架構的設計和代碼的實現。

    五、Log中不能暴露核心算法或機制細節,好比核心算法相關信息、應用和框架間函數的調用流程等。

    六、禁止在循環打印log。在循環條件、頻繁操做、頻繁調用的接口、ACTION_MOVE事件、重複打印等地方,必定要控制好log的使用。在單位時間內,不一樣性質的應用對log的數目有必定的要求,對每條log的大小也有必定的限制。由於大量或者頻繁的log,對app的性能有必定的影響。即使是有log開關控制日誌的輸出與否,字符串的拼接也是會耗掉一些性能和資源的。

    七、打印捕捉到的異常堆棧必須謹慎,如不須要打印堆棧就能定位問題,就儘可能不要打印堆棧,若確實須要堆棧,在同一堆棧,儘可能控制打印頻度。

    八、對於Android源碼中自帶的log,儘可能不要修改。在Event Log中,就嚴禁修改源碼自帶的log。

    九、Log中的TAG,通常以所劃分的功能模塊命名,log信息也最好用類名,方法名拼接爲前綴。這樣作的目的就是在查看log的時候,方便定位,對分析問題頗有幫助。

   上述不只包含使用規範,也包含了部分log使用小技巧。這些規範中有些會根據不一樣公司,不一樣嚴格程度而有所不一樣,而有些則須要統一遵照其規範的,讀者能夠根據具體狀況斟酌。

   

3、Android Studio中查看log

        Android Studio爲開發者提供了良好的log查看工具,開發者能夠經過以下方式打開log視圖:View > Tool Windows > Logcat,或者用默認的快捷鍵 Alt+6 打開/隱藏 Logcat視圖。下面簡單介紹一下該工具的使用。

    一、Logcat中選擇篩選條件  

        以下截圖中,標註了Android Studio中使用Logcat視圖的經常使用功能,開發者能夠根據實際狀況選擇過濾條件。

    二、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,就每每被設置爲紅色

     三、Logcat中的log信息說明

        以下截圖爲筆者打印的某條log,對其中各個字段的進行了說明

        

 

4、寫一份便於使用的Log輔助類

    Log的基本使用技能很容易掌握,可是要能靈活地使用在項目中,仍然有不少技巧須要掌握。

    一、開發者常碰到的場景

    在具體的開發中,開發者每每會遇到以下的情形:

    (1)調試的時候,每每會打印很多的log,用於輔助分析問題,可是要發佈給用戶使用的版本時,這些log必需要關閉掉。

    (2)開發者每每會在代碼中設置一個變量,好比 boolean isDebug等,來控制日誌的打印/關閉。可是每次發佈版本的時候,都須要手動去修改這個值,操做不便,甚至容易忘記。

    (3)發佈給用戶使用的user版本,log被關閉了,出現bug須要分析的時候,log信息太少,每每又讓開發者感到「巧婦難爲無米之炊」,不利於分析問題。

    (4)拿到log信息後,又每每不容易找到這條信息和哪一個功能有關,從哪一個類,哪一個方法中打印出來的。

    (5)有些log須要在user版本中關閉,但有些log須要一直保留,這兩類log的處理,又須要區別對待。

    ······

    諸如此類的情形,想必開發者們都在不斷地經歷着。

    二、輔助工具類代碼

        有經驗的開發者通常都會寫一個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開發者而言,也是實用的。

    三、輔助類的使用和說明。

    (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

 

5、log的獲取

      設計好了log的輸入策略,就能夠獲取log了。筆者接觸到的獲取log的方式主要有以下幾種 

    一、開發工具中獲取。

       好比上文中提到的Android Studio自帶的Logcat視圖,一樣eclipse中也有該視圖,都比較好用。這種方法主要被開發者使用,測試人員通常不會使用IDE中的相似工具。

    二、adb自帶工具 logcat

       該命令功能也比較強大,使用起來很是方便,不須要額外的IDE,電腦上配置好adb,鏈接上手機,在命令框中輸入命令便可。該工具的命令也很多,功能也比較強大,惋惜,筆者對這個功能用得很少,主要使用IDE自帶工具和手機的Mobile Log。

       推薦閱讀:https://blog.csdn.net/liao277218962/article/details/50129009

    三、手機自帶抓log功能

      通常手機也都自帶了抓取log的工具,不一樣的品牌和機型,抓取系統log的方式和log的形式也不盡相同,下面以某比亞的某款機型爲例來講明。

      (1)在撥號盤中輸入暗碼(能夠在網上搜,不一樣品牌暗碼各不一樣,同一手機中抓取log的種類也多樣)就會進入到log工具界面,以下所示:

                     

              能夠看到,能夠抓取的log種類很是多,我們這裏只打開MobileLog。開發者能夠根據實際狀況選擇開啓須要的log,筆者目前爲止,只用到過MoboleLog,-_-

      (2)在使用以前,先點擊「清空」按鈕清理掉以前的log文件, 以避免無關log太多,影響查看有用信息。

      (3)點擊「開始」按鈕,系統就開始抓取log了。

      (4)開始操做手機,復現bug等,這段期間產生的log會被捕獲到。

      (5)操做完成後,點擊「關閉」按鈕,系統會生成日誌文件,在最底部能夠看到日誌的存儲路徑,在該路徑下獲取便可。

                     

 

6、查看及分析log

     拿到日誌文件後,就能夠分析log了。在IDE的視圖工具Logcat中,和adb logcat中獲取的log,基本的查看基本上都會,這裏很少說了。這裏主要講講MobileLog中log分析。

 一、文檔結構

      進入到log文件夾後,會看到以下的文件夾列表

     

      若是開啓了MobileLog,重啓手機或暫停後從新開啓,均會產生一個最新的日誌文件夾。開發者從bug復現最近的一次log開始分析。選擇某個時間段日誌文件夾後點擊,會看到以下界面

     

     通常我們只關注moblie文件夾的內容(筆者目前爲止也只使用過該目錄下的文件)。點擊進入後,會顯示log文件列表,以下所示:

   

  二、分析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的分析技巧也在學習和摸索中,此處慢慢積累經驗,慢慢總結,慢慢更新吧。

  三、源碼解析

      在源碼中有以下的代碼

 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在本地實現。

 

7、第三方工具

     當前在app開發生,也出現了很多比較優秀的管理log的第三方工具,筆者使用過的有兩款:log4j和騰訊的bugly,都比較好用。

       我的建議:使用第三方工具,就必然要導入第三方的jar包,sdk等,無疑會增長app的負載。通常來講,若是本身寫的log輔助類可以輕鬆實現想要的需求,能不用仍是別用吧。固然,我的經驗來看,bugly這類功能不太容易本身實現 -_-

 

8、結語

        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
相關文章
相關標籤/搜索