前言
轉載請聲明,轉自【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 }
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 }
注:這套代碼是根據公司的log使用規范來實現的,筆者當前從事手機系統app的開發,上述的處理辦法也相對偏向系統app方面,但是對於純第三方app開發者而言,也是實用的。
3、輔助類的使用和說明。
(1)打印基本log
根據代碼中的注釋,想必對於這些方法的使用和含義,是很容易理解的。下面簡單演示一下使用的例子
在需要打印log的地方調用 Logger.d(className,methodName,msg);即可,如下演示了輸出后的log
這里提一個小技巧:對於className的獲取,可以采用如下的方法(這里的TAG,就是傳入的Logger.d(...)中的類名了):
public class HandleDemoActivity extends AppCompatActivity { private static final String TAG = HandleDemoActivity.class.getSimpleName(); ...... }
類名.class.getSimpleName()返回的結果就是"HandleDemoActivity",這樣做最大的好處就是,如果類名有變化,這個值也會隨着改變,如果采用硬編碼寫死這個變量,靈活性會比較差。
(2)打印函數調用棧printStackTraceInfo
以下截圖展示了函數的調用棧,對於分析某個方法被調用的軌跡非常有用。第二行printStackTraceInfo()方法是最終捕捉調用棧的地方,可以清晰看到其調用軌跡。
(3)打印異常信息printExceptionInfo(Exception pEx)
該方法主要用打印捕獲的Exception信息,如下截圖一清晰展示地展示了異常原因,發生的地方,已經調用棧等信息。sdk也自帶了e.printStackTrace()方法,由系統自己打印(截圖二)。但是其打印信息被拆分為多條信息打印,在按某個tag進行搜索時,只能搜索到其中含有該tag的信息,而不能整體顯示,自定義的方法就克服了這一點,便於整體查看。當然,讀者可以根據自己愛好來選擇是否用sdk自帶的函數。
截圖一:自定義的異常打印
截圖二:sdk自帶的異常打印
(4)使用Log.isLoggable(tagName, level)
本小結中第1點第(3)條中有提到,調試版本中的log,在user版本中被關閉,這極大地妨礙了對bug的分析。所以在判斷是否允許打印log的條件isTagLoggable(...)中,添加了一個“或”條件,Log.isLoggable(tag, level),就很好地解決了user版本中不能打印部分log的問題。
1)基本使用
加上這條件后,在user版本系統中,只要在命令框中執行如下命令即可:
adb shell setprop log.tag.tagName level
命令中的tagName為輔助類中的TAG值,即FunctionName,level是指希望輸出的log等級下限,比如,如果level為D,則除VERBOSE外,其他等級更高log都會輸出;level為E,就只有ERROR等級log會輸出。針對該輔助類的具體命令為:
adb shell setprop log.tag.FunctionName D
輸入該命令后,凡是以“FunctionName”為tag名,等級在DEBUG及以上的log,就都會輸出了。要想恢復到不可打印的狀態,只要重啟手機即可。
2)相關源碼

1 /** 2 * Checks to see whether or not a log for the specified tag is loggable at the specified level. 3 * 4 * The default level of any tag is set to INFO. This means that any level above and including 5 * INFO will be logged. Before you make any calls to a logging method you should check to see 6 * if your tag should be logged. You can change the default level by setting a system property: 7 * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' 8 * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will 9 * turn off all logging for your tag. You can also create a local.prop file that with the 10 * following in it: 11 * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' 12 * and place that in /data/local.prop. 13 * 14 * @param tag The tag to check. 15 * @param level The level to check. 16 * @return Whether or not that this is allowed to be logged. 17 * @throws IllegalArgumentException is thrown if the tag.length() > 23 18 * for Nougat (7.0) releases (API <= 23) 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);
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);
源碼中也正好給出了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,都比較好用。
- log4j 使用資料:https://blog.csdn.net/zjclugger/article/details/51576156
- bugly 使用資料:https://bugly.qq.com/docs/ 它主要是用來在線捕捉crash/anr等log。
個人建議:使用第三方工具,就必然要導入第三方的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.<YOUR_LOG_TAG> <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.<YOUR_LOG_TAG>=<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 }