1. 程式人生 > >【朝花夕拾】Android Log篇

【朝花夕拾】Android Log篇

前言        

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

 一、Log等級劃分

    Android系統為開發者提供了良好的日誌工具android.util.Log,常用的方法有如下5個,將log的輸出等級也劃分為了5個級別:

    1、Log.v:這裡的v代表Verbose囉嗦的意思,對應的log等級為VERVOSE。採用該等級的log,任何訊息都會輸出。

    2、Log.d:這裡的d代表Debug除錯的意思,對應的log等級為DEBUG。採用該等級的log,除了VERBOSE級別的log外,剩餘的4個等級的log都會被輸出。

    3、Log.i:這裡的i代表information,為一般提示性的訊息,對應的log等級為INFO。採用該等級的log,不會輸出VERBOSE和DEBUG資訊,只會輸出剩餘3個等級的資訊。

    4、Log.w:w代表warning警告資訊,一般用於系統提示開發者需要優化android程式碼等場景,對應的等級為WARN。該級別log,只會輸出WARN和ERROR的資訊。

    5、Log.e:e代表error錯誤資訊,一般用於輸出異常和報錯資訊。該級別的log,只會輸出該級別資訊。一般Android系統在輸出crassh等致命資訊的時候,都會採用該級別的log。

二、Log使用規範

    不同的公司,對Log的使用有不同的要求和規範,以下筆者就工作中碰到的規範來舉例說明Log的使用規範:

    1、在app中,一般不允許使用VERBOSE級別的log,對於INFO、WARN級別的log,允許極少量列印重要資訊。這是工作中的要求,系統原始碼中其實對這三個等級用得也不少,例如,系統列印一般Exception資訊時,就是用的WARN級別log

    2、只有在出現極嚴重錯誤的時候,才允許使用ERROR級別,一般的資訊要是用DEBUG級別。當系統報Fatal Exception的時候,就是用的ERROR級別的log。

    3、使用者的隱私資訊禁止列印,比如:IMEI、手機號、密碼、銀行卡號等。在國外,一些法律也對Log內容做了嚴格的要求。

    4、Log中不要列印太多具體實現的細節,這樣會導致通過log就能猜到架構的設計和程式碼的實現。

    5、Log中不能暴露核心演算法或機制細節,比如核心演算法相關資訊、應用和框架間函式的呼叫流程等。

    6、禁止在迴圈列印log。在迴圈條件、頻繁操作、頻繁呼叫的介面、ACTION_MOVE事件、重複列印等地方,一定要控制好log的使用。在單位時間內,不同性質的應用對log的數目有一定的要求,對每條log的大小也有一定的限制。因為大量或者頻繁的log,對app的效能有一定的影響。即便是有log開關控制日誌的輸出與否,字串的拼接也是會耗掉一些效能和資源的。

    7、列印捕捉到的異常堆疊必須謹慎,如不需要列印堆疊就能定位問題,就儘量不要列印堆疊,若確實需要堆疊,在同一堆疊,儘量控制列印頻度。

    8、對於Android原始碼中自帶的log,儘量不要修改。在Event Log中,就嚴禁修改原始碼自帶的log。

    9、Log中的TAG,一般以所劃分的功能模組命名,log資訊也最好用類名,方法名拼接為字首。這樣做的目的就是在檢視log的時候,方便定位,對分析問題很有幫助。

   上述不僅包含使用規範,也包含了部分log使用小技巧。這些規範中有些會根據不同公司,不同嚴格程度而有所不同,而有些則需要統一遵守其規範的,讀者可以根據具體情況斟酌。

三、Android Studio中檢視log

Android Studio為開發者提供了良好的log檢視工具,開發者可以通過如下方式開啟log檢視:View > Tool Windows > Logcat,或者用預設的快捷鍵 Alt+6 開啟/隱藏 Logcat檢視。下面簡單介紹一下該工具的使用。

    1、Logcat中選擇篩選條件  

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

    2、Log資訊顏色設定

        檢視log的時候,有一個小技巧,為了便於檢視不同等級的log,Android Studio對不同等級的log資訊設定了不同的顏色。開發者也可以根據自己的愛好,自行設定顏色或者其他屬性,這樣,在檢視log的時候,就容易對log等級進行區分,檢視的時候就比較有層次感。設定路徑為:File > Settings > Editor > Colors & Fonts > Android Logcat。如下截圖所示:

              

        設定完成後,用如下程式碼進行測試

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

        logcat檢視中列印的log資訊如下:

             

        雖然開發者可以根據自己的愛好設定log的顏色等屬性,但是筆者還是建議讀者儘量遵守約定俗稱的約定,比如,ERROR級別的log,就往往被設定為紅色。

     3、Logcat中的log資訊說明

        如下截圖為筆者列印的某條log,對其中各個欄位的進行了說明

        

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

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

    1、在具體的開發過程中,開發者常常碰到的比較厭煩的場景

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

    (1)除錯的時候,往往會列印不少的log,用於輔助分析問題,但是要釋出給使用者使用的版本時,這些log必須要關閉掉。

    (2)開發者往往會在程式碼中設定一個變數,比如 boolean isDebug等,來控制日誌的列印/關閉。但是每次釋出版本的時候,都需要手動去修改這個值,操作不便,甚至容易忘記。

    (3)釋出給使用者使用的user版本,log被關閉了,出現bug需要分析的時候,log資訊太少,往往又讓開發者感到“巧婦難為無米之炊”,不利於分析問題。

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

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

    ······

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

    2、編寫一份方便使用的輔助工具類

        有經驗的開發者一般都會寫一個Log的輔助類來儘量規避這些麻煩,筆者在開發中也總結了一套程式碼,如下程式碼所示:

  1 package com.example.demos;
  2 
  3 import android.os.Build;
  4 import android.util.Log;
  5 
  6 public class Logger {
  7     private static final String TAG = "FunctionName";//功能模組名,比如你開發的是相機功能,這裡可以命名為“Camera”,在檢視log的時候,可以檢視到該功能全部log
  8     private static final boolean isLogAnyTime = true;//任何情況下都允許列印的log,無論當前手機韌體版本為“user”、“userdebug”還是“eng”模式
  9 
 10     /**
 11      * 用於根據是否允許列印log來決定是否列印DEBUG等級的log
 12      *
 13      * @param moduleTag  //輸出該log處所在的類名
 14      * @param methodName //輸出該log處所在的方法名
 15      * @param msg        //需要輸出的資訊
 16      */
 17     public static void d(String moduleTag, String methodName, String msg) {
 18         if (isTagLoggable(TAG, Log.DEBUG)) {
 19             Log.d(TAG, createLogPrefix(moduleTag, methodName, msg));
 20         }
 21     }
 22 
 23     /**
 24      * 在程式碼層面,任何情況下都會列印DEBUG等級的日誌(在手機系統中也可以設定允許log列印的等級,這種情況另當別論)
 25      */
 26     public static void alwaysShowD(String moduleTag, String methodName, String msg) {
 27         Log.d(TAG, createLogPrefix(moduleTag, methodName, msg));
 28     }
 29 
 30     public static void e(String moduleTag, String methodName, String msg) {
 31         if (isTagLoggable(TAG, Log.ERROR)) {
 32             Log.e(TAG, createLogPrefix(moduleTag, methodName, msg));
 33         }
 34     }
 35 
 36     public static void alwaysShowE(String moduleTag, String methodName, String msg) {
 37         Log.e(TAG, createLogPrefix(moduleTag, methodName, msg));
 38     }
 39 
 40     /**
 41      * 用於列印方法的呼叫棧,即該函式一層一層的呼叫關係列表
 42      */
 43     public static void printStackTraceInfo() {
 44         if (isTagLoggable(TAG, Log.DEBUG)) {
 45             Log.d(TAG, Log.getStackTraceString(new Throwable()));
 46         }
 47     }
 48 
 49     /**
 50      * 獲取捕捉到的Exception資訊,並轉化為字串
 51      */
 52     public static void printExceptionInfo(Exception pEx) {
 53         String _exStr = pEx.toString() + "\n";
 54         StackTraceElement[] stackTraceElements = pEx.getStackTrace();
 55         if (stackTraceElements == null) {
 56             Log.w(TAG, _exStr);
 57         }
 58         for (StackTraceElement se : stackTraceElements) {
 59             _exStr += ("at " + se.getClassName() + "." + se.getMethodName() + "(" + se.getFileName() + ":" + se.getLineNumber() + ")\n");
 60         }
 61         Log.w(TAG, _exStr);
 62     }
 63 
 64     /**
 65      * 判斷當前log是否允許輸出
 66      * 對Log.isLoggable(tag,level)的使用,後文會做進一步的說明
 67      * @param tag   官方:the tag to check
 68      * @param level 官方:the level to check
 69      * @return true 表示允許輸出,false表示不允許輸出
 70      */
 71     private static boolean isTagLoggable(String tag, int level) {
 72         return Log.isLoggable(tag, level) || isDebugMode() || isLogAnyTime;
 73     }
 74 
 75     /**
 76      * 將各個引數按照一定的格式組合,便於log檢視
 77      *
 78      * @param moduleTag  傳入所在的類名
 79      * @param methodName 傳入所在的方法名
 80      * @param msg        要輸出的資訊
 81      * @return 組合後的字串
 82      */
 83     private static String createLogPrefix(String moduleTag, String methodName, String msg) {
 84         StringBuffer buffer = new StringBuffer();
 85         buffer.append("[").append(moduleTag).append("]").append(methodName).append(":").append(msg);
 86         return buffer.toString();
 87     }
 88 
 89     /**
 90      * 手機的系統一般有“user”、“userdebug”、“eng”版本,“user”版本是最終發給使用者使用的版本,
 91      * 而另外兩種為工程師除錯的版本,可以對手機做更多的操作,比如root,remount等。往往開發者
 92      * 用於除錯的大部分log,在發給使用者使用時(機user版本),必須要關閉掉。
 93      *
 94      * @return true 表示當前手機系統版本為“eng”或者“userdebug”版本
 95      * false表示“user”版本
 96      */
 97     private static boolean isDebugMode() {
 98         return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE);
 99     }
100 }

注:這套程式碼是根據公司的log使用規範來實現的,筆者當前從事手機系統app的開發,上述的處理辦法也相對偏向這方面,但是對於純第三方app開發者而言,也是可以參考的。

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

    (1)列印基本log

         根據程式碼中的註釋,想必對於這些方法的使用和含義,是很容易理解的。下面簡單演示一下使用的例子

         在需要列印log的地方呼叫 Logger.d(className,methodName,msg);即可,如下演示了輸出後的log

     

    (2)列印函式呼叫棧printStackTraceInfo

       以下截圖展示了函式的呼叫棧,對於分析某個方法被呼叫的軌跡非常有用。第二行printStackTraceInfo()方法是最終捕捉呼叫棧的地方,可以清晰看到其呼叫軌跡。

       

    (3)列印異常資訊printExceptionInfo(Exception pEx)

       該方法主要用列印捕獲的Exception資訊,如下截圖一清晰展示地展示了異常原因,發生的地方,已經呼叫棧等資訊。sdk也自帶了e.printStackTrace()方法,由系統自己列印(截圖二)。但是其列印資訊被拆分為多條資訊列印,在按某個tag進行搜尋時,只能搜尋到其中含有該tag的資訊,而不能整體顯示,自定義的方法就克服了這一點,便於整體檢視。當然,讀者可以根據自己愛好來選擇是否用sdk自帶的函式。

                 

                                                      截圖一:自定義的異常列印

                 

                                                截圖二:sdk自帶的異常列印

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

       本小結中第1點第(3)條中有提到,除錯版本中的log,在user版本中被關閉,這極大地妨礙了對bug的分析。所以在判斷是否允許列印log的條件isTagLoggable(...)中,添加了一個“或”條件,Log.isLoggable(tag, level),就很好地解決了user版本中不能列印部分log的問題。加上這條件後,在user版本系統中,只要在命令框中執行如下命令即可:

1 adb shell setprop log.tag.tagName level

       命令中的tagName為輔助類中的TAG值,即FunctionName,level是指希望輸出的log等級下限,比如,如果level為D,則除VERBOSE外,其他等級更高log都會輸出;level為E,就只有ERROR等級log會輸出。針對該輔助類的具體命令為:

1 adb shell setprop log.tag.FunctionName D

輸入該命令後,凡是以“FunctionName”為tag名,等級在DEBUG及以上的log,就都會輸出了。要想恢復到不可列印的狀態,只要重啟手機即可。

五、log的獲取

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

    1、開發工具中獲取。

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

    2、adb自帶工具 logcat

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

    3、手機自帶抓log功能

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

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

                     

              可以看到,可以抓取的log種類非常多,咱們這裡只打開MobileLog。開發者可以根據實際情況選擇開啟需要的log,筆者目前為止,只用到過MoboleLog,-_-

      (2)在使用之前,先點選“清空”按鈕清理掉之前的log檔案, 以免無關log太多,影響檢視有用資訊。

      (3)點選“開始”按鈕,系統就開始抓取log了。

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

      (5)操作完成後,點選“關閉”按鈕,系統會生成日誌檔案,在最底部可以看到日誌的儲存路徑,在該路徑下獲取即可。

                     

六、檢視及分析log

     拿到日誌檔案後,就可以分析log了。在IDE的檢視工具Logcat中,和adb logcat中獲取的log,基本的檢視基本上都會,這裡不多說了。這裡主要講講MobileLog中log分析。進入到log資料夾後,會看到如下的資料夾列表

     

如果開啟了MobileLog,重啟手機或暫停後重新開啟,均會產生一個最新的日誌資料夾。開發者從bug復現最近的一次log開始分析。選擇某個時間段日誌資料夾後點擊,會看到如下介面

    

一般咱們只關注moblie資料夾的內容(筆者目前為止也只使用過該目錄下的檔案)。點選進入後,會顯示log檔案列表,如下所示:

檔名中包含了機型、版本資訊,以及檔案中log的型別。一般咱們也只需要關注crash、main檔案,有時候也會關注system日誌檔案。

  • crash檔案中收集了系統中crash的log,首先分析這個檔案,看是否有和自己專案相關的crash資訊。
  • main檔案,咱們前文中講到的新增的log,允許列印的,都會被收集到該檔案中。
  • system檔案,收集系統的log,系統框架中自帶的log會體現在該檔案中,偶爾有需要使用。
  • 其他檔案使用得不多,筆者暫時還沒有碰到要使用剩餘這幾個檔案的場景。

       在crash檔案中,可以清晰地看到crash發生的時間,引起crash的程序及包名等資訊。這裡要注意crash的時間,如果和自己復現的場景時間差得比較遠(比如10分鐘以上),就可能和自己要分析的問題沒太大的關聯度。

     

        在main檔案中,往往包含了大量的log資訊。前面講到的logcat檢視或adb logcat捕獲的log,以及不同機型手機中不同型別的log,其實基本結構基本相同。單條資訊中也都包含了日期、時間、程序號、執行緒號、log等級、TAG,msg等資訊。在分析這些log的時候,筆者這裡提幾個經常用的小技巧:

  • 選一個好用的文字編輯器。筆者和周圍的同事基本上用的都是Notepad++,對查詢資訊非常有幫助,對於該工具的使用技巧,讀者可以自己網上搜索一下。
  • 結合自己新增log的時候的設計,可以快速根據功能模組、類名、方法名等關鍵資訊,篩選出關聯度高的資訊來。
  • 每一個app一般對應一個程序號,如果程序號中途變化了,說明中途該app發生了crash,可以在程序號變化點附近查詢bug原因。

   筆者對MobileLog的分析技巧也在學習和摸索中,此處慢慢積累經驗,慢慢總結,慢慢更新吧。

七、第三方工具使用

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

八、結語

log的使用算是anroid開發中一個比較基礎的技能了,也一個非常實用的技能,是開發中時時刻刻都要用到的技能。本文所講的內容大多都算比較基礎,當然也包含了一些平時容易忽視的知識點,基本上沒有什麼講原理的地方。筆者在MobileLog分析等不少方面,經驗也還比較淺,也在不斷學習摸索中和總結中,希望讀者們能多多指教,萬分感謝!