天天看點

【朝花夕拾】Android Log篇

前言        

       原文:

【朝花夕拾】Android Log篇

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

       本文主要内容如下:

【朝花夕拾】Android 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視圖的常用功能,開發者可以根據實際情況選擇過濾條件。

【朝花夕拾】Android Log篇

    2、Log資訊顔色設定

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

【朝花夕拾】Android Log篇
        設定完成後,用如下代碼進行測試

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資訊如下:

【朝花夕拾】Android Log篇

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

     3、Logcat中的log資訊說明

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

【朝花夕拾】Android 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 }           

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

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

    (1)列印基本log

         根據代碼中的注釋,想必對于這些方法的使用和含義,是很容易了解的。下面簡單示範一下使用的例子

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

【朝花夕拾】Android Log篇

    (2)列印函數調用棧printStackTraceInfo

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

【朝花夕拾】Android Log篇

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

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

【朝花夕拾】Android Log篇

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

【朝花夕拾】Android Log篇

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

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

       本小結中第1點第(3)條中有提到,調試版本中的log,在user版本中被關閉,這極大地妨礙了對bug的分析。是以在判斷是否允許列印log的條件isTagLoggable(...)中,添加了一個“或”條件,Log.isLoggable(tag, level),就很好地解決了user版本中不能列印部分log的問題。加上這條件後,在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,就都會輸出了。要想恢複到不可列印的狀态,隻要重新開機手機即可。

       推薦閱讀:

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工具界面,如下所示:

【朝花夕拾】Android Log篇

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

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

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

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

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

【朝花夕拾】Android Log篇

六、檢視及分析log

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

【朝花夕拾】Android Log篇

如果開啟了MobileLog,重新開機手機或暫停後重新開啟,均會産生一個最新的日志檔案夾。開發者從bug複現最近的一次log開始分析。選擇某個時間段日志檔案夾後點選,會看到如下界面

【朝花夕拾】Android Log篇

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

【朝花夕拾】Android Log篇

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

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

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

【朝花夕拾】Android Log篇

        在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分析等不少方面,經驗也還比較淺,也在不斷學習摸索中和總結中,希望讀者們能多多指教,萬分感謝!

繼續閱讀