前言 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 }
三、源碼解讀
除了在註釋中明確的解釋說明外,咱們也能夠留意一下額外的信息
(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 }
注:這套代碼是根據公司的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.<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)源碼解讀
依據以上源碼及註釋,筆者提取了部分信息:
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日誌文件,其使用狀況以下。
(2)分析crash文件log
在crash文件中,能夠清晰地看到crash發生的時間,引發crash的進程及包名等信息。這裏要注意crash的時間,若是和本身復現的場景時間差得比較遠(好比10分鐘以上),就可能和本身要分析的問題沒太大的關聯度。
(3)分析main文件log
在main文件中,每每包含了大量的log信息。前面講到的logcat視圖或adb logcat捕獲的log,以及不一樣機型手機中不一樣類型的log,其實基本結構基本相同。單條信息中也都包含了日期、時間、進程號、線程號、log等級、TAG,msg等信息。以下圖所示:
在分析這些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);
源碼中也正好給出了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.<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 }