Log最佳實(shí)踐

本文會(huì)不定期更新,推薦watch下項(xiàng)目。
如果喜歡請(qǐng)star,如果覺得有紕漏請(qǐng)?zhí)峤籭ssue,如果你有更好的點(diǎn)子可以提交pull request。
本文的示例代碼主要是基于loggerLogUtilstimber進(jìn)行編寫的,如果想了解更多請(qǐng)查看他們的詳細(xì)解釋。
我很推薦大家多多進(jìn)行對(duì)比,選擇適合你自己的庫來使用。

本文固定連接:https://github.com/tianzhijiexian/Android-Best-Practices


一、背景

Android中的log是這么寫的:

Log.d(TAG, "This is a debug log");

android.util.Log類做的事情很簡(jiǎn)單,符合kiss原則,但是隨著業(yè)務(wù)的不斷發(fā)展,logcat中就會(huì)有多個(gè)部門的各種log,不同手機(jī)系統(tǒng)自己的一些log也會(huì)參雜進(jìn)來,逼迫我們要擴(kuò)展log類。

二、需求

  1. 我才不要每次打log都去想tag叫什么名字呢
  2. 通常情況下請(qǐng)自動(dòng)把當(dāng)前類名作為默認(rèn)的tag,但也允許我自由指定
  3. 我希望我寫的模板式代碼越少越好,一個(gè)logd就能打印一切
  4. 我要打印出list,map,json,pojo這樣的對(duì)象
  5. 我的log絕對(duì)不要和其余的雜亂log混在一起
  6. log信息過長(zhǎng)后應(yīng)該要自動(dòng)換行,我不允許我的log打印不全
  7. 我要我的log變的好看,直觀,就是美
  8. log中還要能顯示我當(dāng)前的線程名,方便我調(diào)試多線程
  9. 我打出的log后面要根上這個(gè)log的地址,可以直接外鏈到log的位置
  10. release包中不能泄漏我高傲的log,但只要我想讓它顯示,release版本也阻擋不了我
  11. 在release版本中殘留的log代碼應(yīng)該對(duì)app運(yùn)行效率影響極低
  12. 它能自動(dòng)將try-catch住的crash通過log上傳到Crashlytics

回看這些需求,不合理么?其實(shí)很合理,我們的宗旨就是讓無意義的重復(fù)代碼去死,如果死不掉就交給機(jī)器來做。我們應(yīng)該做那些真正需要我們做的事情,而不是像一個(gè)沒思想的猿猴一般整天寫模板式代碼。這才是程序員思維,而不是程序猿思維!

注意:我希望只要寫真正有意義的內(nèi)容!

三、實(shí)現(xiàn)

分析上述的需求后,我將其分為四類: 使用、顯示和擴(kuò)展。

使用篇

建立包裝類

無論一個(gè)第三方庫有多好,我還是推薦不直接使用它,因?yàn)槟愫苡锌赡軙?huì)去替換這個(gè)第三方庫,而且一個(gè)第三方庫肯定無法滿足各種奇葩需求。所以,對(duì)于網(wǎng)絡(luò)庫、圖片庫和log庫來說,我們應(yīng)該事先考慮在上面封裝一層。

我們建立一個(gè)包裝類,用這個(gè)包裝類用來包裹Logger(logger是本文介紹的一個(gè)log庫),下面是包裝類的代碼片段:

public static void d(@Nullable String info, Object... args) {
    if (!mIsOpen) { // 如果把開關(guān)關(guān)閉了,那么就不進(jìn)行打印
        return;
    }
    Logger.d(info, args);
}

對(duì)于包裝類的起名最好不要和“Log”這個(gè)類似,能有明顯的區(qū)別最好,一是防止自己手抖寫錯(cuò)了,二是方便review的時(shí)候能快速檢查出有沒有誤用原始的Log。

自動(dòng)打tag

默認(rèn)情況下可以把當(dāng)前類名作為TAG的默認(rèn)值,我們可以通過下面代碼來得到當(dāng)前類名:

private static String getClassName() {
    // 這里的數(shù)組的index,即2,是根據(jù)你工具類的層級(jí)取的值,可根據(jù)需求改變
    StackTraceElement thisMethodStack = (new Exception()).getStackTrace()[2]; 
    String result = thisMethodStack.getClassName();
    int lastIndex = result.lastIndexOf(".");
    result = result.substring(lastIndex + 1, result.length());
    return result;
}

這樣我們就輕易的擺脫了tag的糾纏。
需要注意的是,獲取堆棧的方法是有性能消耗的,所以在主線程的log可能會(huì)引起一些卡頓,所以強(qiáng)烈建議在release版本中不要使用這個(gè)方法。

這個(gè)方法來自于豪哥的建議,這里感謝豪哥的意見。

自定義tag

除了自動(dòng)打tag外,我們肯定要讓其支持自定義tag:

public static void d(@NonNull String tag, String info, Object... args) {
    Logger.t(tag).d(info, args);
}

這個(gè)d(tag, info, args...)是上面d(info, args...)的擴(kuò)展,這里要注意的是tag的選取。

常用的做法是用getSimpleName的方式來得到tag,但如果你加了混淆,很多類(Activity、View不一定會(huì)被混淆)就會(huì)被混淆為a/b/c這樣的單詞。因此,如果你的log要出現(xiàn)在混淆的包里的,我強(qiáng)烈建議去手動(dòng)設(shè)置tag值,否則打出來的log就是很難過濾的了。

至于如何手動(dòng)設(shè)置tag的值,下面會(huì)講到logt這個(gè)快捷命令。

自定義全局tag和tag前綴

如果你的項(xiàng)目很龐大或者采用了插件化和組件化方案,那么你肯定會(huì)涉及到多人開發(fā)的問題。底層平臺(tái)是暴露統(tǒng)一的log接口,但是上層開發(fā)人員種類繁多,如何在繁雜的log中找到自己部門的自己關(guān)心的log呢?

在這種情況下我們可以采用如下兩種方案:

  1. 自行調(diào)試時(shí)關(guān)閉無關(guān)部門的log輸出
  2. 每個(gè)部門有自定義的tag前綴

對(duì)于方案一,我們本身的log系統(tǒng)底層采用的是timber,它本身就是通過“種樹”的方式進(jìn)行l(wèi)og分發(fā)的,我們只需要在我們項(xiàng)目的最開始調(diào)用

Logger.uprootAll();
// or
Timber.uprootAll();

將所有之前的log通道移除,這樣就清空了無用的log了。

相比起方案一的簡(jiǎn)單粗暴,方案二倒是溫和實(shí)用的多。我們通過在logger初始化設(shè)置一個(gè)tagPrefix,這個(gè)前綴就會(huì)伴隨著我們私有項(xiàng)目的所有l(wèi)og了,以后直接搜索這個(gè)前綴就可以過濾出想要的信息了。

開啟和關(guān)閉log

有時(shí)候在調(diào)試過程中可能會(huì)要支持測(cè)試同學(xué)的動(dòng)態(tài)關(guān)閉和開啟log的功能。

Logger.closeLog();
Logger.openLog(Log.INFO);

這個(gè)操作可以支持在應(yīng)用運(yùn)行的時(shí)的任何時(shí)候進(jìn)行開關(guān)。

將Log代碼快捷模板

有人說我們IDE不都有代碼提示了么,你還想怎么簡(jiǎn)化log的輸入呢?這里可以利用as的模板提示的功能:

我們可以模仿原有的模板來做自己的代碼模板,簡(jiǎn)化模板式代碼的輸入。至于具體模仿的方式我就不手把手教了,相當(dāng)簡(jiǎn)單。下面僅展示下自帶的log模板的使用:

生成TAG:


自動(dòng)填寫參數(shù)和方法名:

顯示篇

讓log更加美觀

讓log的輸出直觀、美觀其實(shí)很簡(jiǎn)單,就是在輸出前做點(diǎn)字符串拼接的工作,比如加上下面這行橫線。

private static final String BOTTOM_BORDER = "╚═══════════════════════════";

因?yàn)樽隽撕芏嗥唇拥墓ぷ鳎院每吹膌og也是消耗性能的。我的習(xí)慣是調(diào)試完畢后立刻刪除無用的log,這樣既能減少性能影響,也能減少同事的閱讀代碼的負(fù)擔(dān)。采用輕量級(jí)美化后效果如下:

顯示當(dāng)前方法名、所在類并加超鏈

這個(gè)功能其實(shí)ide是原生支持的,不相信的話你隨便用原生的log打印出onCreate: (MainActivity.java:31)試試。

我們可以通過下面的方法來做到更好的效果:

    private static String callMethodAndLine() {
        String result = "at ";
        StackTraceElement thisMethodStack = (new Exception()).getStackTrace()[1];
        result += thisMethodStack.getClassName()+ "."; //  當(dāng)前的類名(全名)
        result += thisMethodStack.getMethodName();
        result += "(" + thisMethodStack.getFileName();
        result += ":" + thisMethodStack.getLineNumber() + ")  ";
        return result;
    }

這里同樣需要注意的是類在混淆后是得不到正確的名稱的,所以可以酌情讓activity、fragment、view不被混淆,具體方案還是看自己的取舍。

增加當(dāng)前線程的信息

當(dāng)你調(diào)試過多線程,你就會(huì)發(fā)現(xiàn)log中帶有線程的信息是很方便的。

Thread.currentThread().getName()

Logger的尾巴上會(huì)帶有線程的名字,方便大家進(jìn)行調(diào)試。

支持POJO、Map、Collection、jsonStr、Array

這個(gè)需求實(shí)現(xiàn)起來也比較容易:

  • 如果是POJO,我們可用反射得到對(duì)象的類變量,通過字符串拼接的方式最終輸出值
  • 如果是map等數(shù)組結(jié)構(gòu),那么就用其內(nèi)部的遍歷依次輸出值和內(nèi)容
  • 如果是json的字符串,就需要判斷json的{},[]這樣的特殊字符進(jìn)行換行處理

至于具體是如何實(shí)現(xiàn)的,大家移步去看源碼就好,這個(gè)不是重點(diǎn),重點(diǎn)是結(jié)果:

不推薦打印每次網(wǎng)絡(luò)請(qǐng)求的json,只推薦在調(diào)試某個(gè)數(shù)據(jù)的時(shí)候進(jìn)行打印,否則信息太多,而且效率很低,不實(shí)用。

自定義輸出樣式

我們看到了orhanobut/loggerelvishew/xLog都十分好看,但是tianzhijiexian/logger的log看起來就沒那么美觀了,所以這個(gè)庫支持了自定的style,讓使用者可以自定義輸出樣式。

PrintStyle.java

public abstract class PrintStyle {

    @Nullable
    protected abstract String beforePrint();

    @NonNull
    protected abstract String printLog(String message, int line, int wholeLineCount);

    @Nullable
    protected abstract String afterPrint();
}

這個(gè)抽象類提供了三個(gè)方法,用來得到log打印前,打印時(shí),打印后的內(nèi)容,我們可以通過它來實(shí)現(xiàn)自定義的樣式。

**使用XLog樣式后的輸出: **

PS:Logger的不美觀其實(shí)是折衷的結(jié)果。美觀必然會(huì)帶來數(shù)據(jù)的冗余,但原始的log卻又不足夠清晰。Logger最終選擇了一個(gè)輕量的log樣式,既保證了清晰易辨認(rèn)又不會(huì)帶來過多的冗余信息。

支持超長(zhǎng)的log信息

有時(shí)候網(wǎng)絡(luò)的返回值是很長(zhǎng)的,android.util.Log類是有最大長(zhǎng)度限制的。為了解決這個(gè)問題,我們只需要判斷這個(gè)字符串的長(zhǎng)度,然后手動(dòng)讓其換行即可。

private static final int CHUNK_SIZE = 4000;

if (length <= CHUNK_SIZE) {
    logContent(logType, tag, msg);
} else {
    for (int i = 0; i < length; i += CHUNK_SIZE) {
        int count = Math.min(length - i, CHUNK_SIZE);
        //create a new String with system's default charset (which is UTF-8 for Android)
        logContent(logType, tag, new String(bytes, i, count));
    }
}

自定義過濾規(guī)則

當(dāng)崩潰出現(xiàn)的時(shí)候,有時(shí)候會(huì)將我們的log清屏,大大影響了我們的調(diào)試工作。所以我們可以在合適的時(shí)候利用Edit Filter Configuration這個(gè)功能。

Edit Filter Configuration十分強(qiáng)大,并且支持正則。一般情況下使用Show only selected application就搞定了,是否使用Edit Filter Configuration就看你的具體場(chǎng)景了。

擴(kuò)展篇

增加自動(dòng)化或強(qiáng)制開關(guān)

要區(qū)分release和debug版本,可以用自帶的BuildConfig.DEBUG變量,用這個(gè)也就可以控制是否顯示log了。做個(gè)強(qiáng)制開關(guān)也很簡(jiǎn)單,在log初始化的最后判斷強(qiáng)制開關(guān)是否打開,如果打開那么就覆蓋之前的顯示設(shè)置,直接顯示log。轉(zhuǎn)為代碼就是這樣:

public class BaseApplication extends Application {

    // 定義是否是強(qiáng)制顯示log的模式
    protected static final boolean LOG = false;

    @Override
    public void onCreate() {
        Logger.initialize(
            new Settings()
                    .setLogPriority(BuildConfig.DEBUG ? Log.VERBOSE : Log.ASSERT)
        );
        
        // 如果是強(qiáng)制顯示log,那么無論在什么模式下都顯示log
        if (LOG) {
            Logger.getSettings().setLogPriority(Log.VERBOSE)
        }
    }
}

以后要是需要做log的開關(guān),那么只需要通過settings重設(shè)log級(jí)別即可:

Logger.getSettings().setLogPriority(Log.ASSERT); // close log

解決log字符拼接的效率影響

多參數(shù)log信息應(yīng)該利用占位符進(jìn)行打印,盡量避免手動(dòng)拼接字符串。這樣好處是:在關(guān)閉log后就不會(huì)進(jìn)行字符串的拼接工作了,減少log語句在release版本中的性能影響。

封裝類.d("test %s%s", "v", 5); // test v5
public static void d(@Nullable String info, Object... args) {
    if (!mIsOpen) { // 如果把開關(guān)關(guān)閉了,自然就不進(jìn)行字符串拼接
        return;
    }
    Logger.d(info, args); // 內(nèi)部會(huì)做String.format()
}

這條來自朋友helder的建議,感謝!

通過混淆剔除log代碼

如果你確定你的log代碼在release版本中是無需存在的,那么我分享一個(gè)方案來幫你干掉它。

比如你的混淆配置文件叫proguard-rules.pro,里面有如下代碼:

-assumenosideeffects class kale.log.LL { // 假設(shè)我們的log類是LL
    public static *** d(...); // public static void d(...);
    public static *** i(...);
    public static *** v(...);
}

然后在build.gradlez中啟用混淆:

buildTypes {
        release {
            minifyEnabled true
            shrinkResources true // 是否去除無效的資源文件
            // 注意是用proguard-android-optimize.txt而不是proguard-android.txt
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }

要令assumenosideeffects生效,就需要開啟混淆中的優(yōu)化選項(xiàng),而默認(rèn)的proguard-android.txt是不會(huì)開啟優(yōu)化選項(xiàng)的。如果我們需要開啟混淆的話,那么建議我們采用 proguard-android-optimize.txt。

proguard-android-optimize的全部?jī)?nèi)容如下:

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html

# Optimizations: If you don't want to optimize, use the
# proguard-android.txt configuration file instead of this one, which
# turns off the optimization flags.  Adding optimization introduces
# certain risks, since for example not all optimizations performed by
# ProGuard works on all versions of Dalvik.  The following flags turn
# off various optimizations known to have issues, but the list may not
# be complete or up to date. (The "arithmetic" optimization can be
# used if you are only targeting Android 2.0 or later.)  Make sure you
# test thoroughly if you go this route.
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
-dontpreverify

# The remainder of this file is identical to the non-optimized version
# of the Proguard configuration file (except that the other file has
# flags to turn off optimization).

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

上面的注釋就是采用優(yōu)化方案來剔除log的風(fēng)險(xiǎn)點(diǎn),所以要慎重使用!!!

這里也提到了一般推薦用proguard-android.txt來做混淆方案,如果你要是用了proguard-android-optimize.txt的話,請(qǐng)一定要測(cè)試充分在發(fā)布app。

將try-catch的信息通過log上傳到Crashlytics

我們有時(shí)候?yàn)榱朔烙硞€(gè)未知原因的崩潰,經(jīng)常會(huì)進(jìn)行try-catch。這樣雖然讓其沒崩潰,但是也隱藏了錯(cuò)誤,以至于我們始終沒有辦法弄懂錯(cuò)誤出現(xiàn)的原因。
我希望可以通過把catch的異常通過log系統(tǒng)分發(fā)到崩潰分析網(wǎng)站上(如:Crashlytics),這樣既能防御問題,又可以幫助開發(fā)者知道崩潰產(chǎn)生的原因,方便以后針對(duì)性的進(jìn)行處理。

代碼參考自:https://blog.xmartlabs.com/2015/07/09/Android-logging-with-Crashlytics-and-Timber/

模擬

    /**
     * 這里模擬后端給客戶端傳值的情況。
     *
     * 這里的id來自外部輸入,如果外部輸入的值有問題,那么就可能崩潰。
     * 但理論上是不會(huì)有數(shù)據(jù)異常的,為了不崩潰,這里加try-catch
     */
    private void setRes(@StringRes int resId) {
        TextView view = new TextView(this);

        try {
            view.setText(resId); // 如果出現(xiàn)了崩潰,那么就會(huì)調(diào)用崩潰處理機(jī)制
        } catch (Exception e) {
            // 防御了崩潰
            e.printStackTrace();
            
            // 把崩潰的異常和當(dāng)前的上下文通過log系統(tǒng)分發(fā)
            Logger.e(e, "res id = " + resId);
        }
    }

接下來,我們建立一個(gè)crash分發(fā)tree:

public class CrashlyticsTree extends Timber.Tree {

    @Override
    protected void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable t) {
        if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) {
            // 只分發(fā)異常
            return;
        }

        if (t == null && message != null) {
            Crashlytics.logException(new Exception(message));
        } else if (t != null && message != null) {
            Crashlytics.logException(new Exception(message, t));
        } else if (t != null) {
            Crashlytics.logException(t);
        }
    }
}

// ---------------

if (!BuildConfig.DEBUG) {    // for release    
    Logger.plant(new CrashlyticsTree()); // plant a tree
}

一旦用戶發(fā)生了崩潰,我們現(xiàn)在就可以通過Crashlytics進(jìn)行分析,這樣的錯(cuò)誤會(huì)自動(dòng)歸檔在Crashlytics報(bào)表的non-fatals中。通過這樣的方式,可以方便我們排查出真正的問題,解決后就可以真正去掉這個(gè)try-catch了。

注意:
因?yàn)槲覀冇行╁e(cuò)誤是不希望上傳的,有些是希望上傳的,所以我建議在使用Logger.e()的時(shí)候,通過你的包裝類來做個(gè)處理(加參數(shù)或加方法),讓使用者明確這個(gè)log將通向何方,不希望引起理解混亂。

增加log的擴(kuò)展性

正如上面提到的,我們的log可能需要分發(fā)到不同的系統(tǒng),這也是我采用timber的原因。我們除了將線上的錯(cuò)誤分發(fā)到崩潰統(tǒng)計(jì)系統(tǒng)外,也可能要將log保存到sd卡或是做其他的處理,所以目前l(fā)ogger利用timber的tree實(shí)現(xiàn)了分發(fā)的功能。

Logger內(nèi)部的實(shí)現(xiàn):

public static void plant(Timber.Tree tree) {
    Timber.plant(tree);
}

關(guān)于如何plant可以參考下Timber的具體代碼。

通過自定義lint來規(guī)范log

大多數(shù)團(tuán)隊(duì)會(huì)定義自己的log類來進(jìn)行l(wèi)og的打印,我們最好可以通過自定義的lint來在代碼編寫時(shí)防止開發(fā)者錯(cuò)用log類。

詳細(xì)的內(nèi)容可以參考:《Android自定義Lint實(shí)踐》

利用IDEA的debug工具打log

上文中我就提到了可以利用as的調(diào)試模式來加速debug,下面分享下兩個(gè)和log有關(guān)的經(jīng)驗(yàn)。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    
    private int index = 0;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(v-> {
                index = 123;
                Log.d(TAG, "onClick: index = " + index);
                index++;
            }
        );
    }
}

1.通過斷點(diǎn)來代替log
如果你的目的僅僅是進(jìn)行調(diào)試,那么以我的經(jīng)驗(yàn)來看,我推薦通過斷點(diǎn)進(jìn)行操作而不是log。

斷點(diǎn)的好處是隨時(shí)熱部署不用重新跑一次代碼,而且完全無入侵。如果你要調(diào)試的是再某個(gè)場(chǎng)景下的情況,你也可以通過條件斷點(diǎn)進(jìn)行處理:

2.通過console熱部署打印log信息
我通過debug工具,可以在任意位置打印出任意對(duì)象的值,通過這種方式就可以精準(zhǔn)調(diào)試一些信息了。
下圖是我讓其在不中斷運(yùn)行的情況下打印index的值。

3.動(dòng)態(tài)設(shè)置值
有時(shí)候某種分支需要在某個(gè)情況下才能走到,我可以利用debug的setValue(F12)方法動(dòng)態(tài)設(shè)置值,比如我把下面的123改成了520,最終在終端打印出的信息也會(huì)變成520。整個(gè)過程對(duì)原本代碼完全屏蔽,無入侵。

PS:更多的調(diào)試技巧可以查看Android-Best-Practices中的推薦的調(diào)試技巧的文章。

因地制宜的使用log

雖然我提出了上面的思路和方案,但我并不能確保可以滿足所有的需求,我給出下面的思維流程,方便大家隨機(jī)應(yīng)變:

  1. 盡量用as的debug模式下的log系統(tǒng),無入侵。不用寫代碼就能打log,十分方便。
  2. 如果真的要打log做調(diào)試,先用debug和error級(jí)別,提交代碼時(shí)務(wù)必記得清除。
  3. 如果提交的代碼中需要在某個(gè)關(guān)鍵點(diǎn)打log,或者要持續(xù)調(diào)試,可以用info以上的log。
  4. 在realse中用自己的log包裝類的開關(guān)做處理,這樣方便在公司內(nèi)部測(cè)試時(shí)可以查看到log。
  5. 如果一些信息需要在用戶版本中保留,優(yōu)先考慮數(shù)據(jù)統(tǒng)計(jì)的方式進(jìn)行關(guān)鍵點(diǎn)的打點(diǎn)。
  6. 如果真的要在發(fā)布出去的apk中帶著log,只保留info級(jí)別以上的,不輕易把info級(jí)別之下的信息漏出去。

通過在線工具瀏覽log

開發(fā)們都會(huì)遇到測(cè)試在測(cè)試的時(shí)候出現(xiàn)了崩潰但是當(dāng)時(shí)忘記抓到log了,我們拿到手機(jī)的時(shí)候已經(jīng)沒有了當(dāng)時(shí)的現(xiàn)場(chǎng)信息。一般的做法是拿到手機(jī)進(jìn)入adb shell,然后dump出log信息。
tianzhijiexian/Logcat利用了公司內(nèi)網(wǎng)和手機(jī)作服務(wù)器的能力,讓開發(fā)者或測(cè)試可以遠(yuǎn)程看到log信息。這種方式不用進(jìn)行usb鏈接,而且對(duì)于測(cè)試人員無學(xué)習(xí)成本,同樣也適合于遠(yuǎn)程和用戶一對(duì)一進(jìn)行調(diào)式的情形。

四、總結(jié)

我們可以看到即使一行代碼的log都有很多點(diǎn)是可優(yōu)化的,還明白了我們之前一直寫的模板式代碼是多么的枯燥乏味。
通過這篇文章,希望大家可以看到一個(gè)優(yōu)化編碼的思維過程,也希望大家去嘗試下logger這個(gè)庫。當(dāng)然,我知道還是有很多人不喜歡,那么不妨提出更好的解決方案來一起討論,不滿意可以提issue。
要知道精品永遠(yuǎn)是個(gè)位數(shù),而中庸的東西永遠(yuǎn)是層出不窮的。我希望大家多提意見齊心協(xié)力優(yōu)化出一個(gè)精品,而不是花時(shí)間去在平庸的選項(xiàng)中做著選擇難題。

五、尾聲

在文章中我給出了通過idea的debug模式下打印log的方法,目的是即使你有了這個(gè)log庫,但我仍舊希望你可以能找到更好的方法來達(dá)到調(diào)試的目的。擁有技巧,使用技巧,最終化為無形才是最高境界。相信我們的最終目的是一致的,那就是讓開發(fā)越來越簡(jiǎn)便,越來越優(yōu)雅~

最后說下我沒直接用文章開頭那幾個(gè)庫的原因,logger的庫很漂亮,但是冗余行數(shù)過多,調(diào)試多行的數(shù)據(jù)就會(huì)受到信息干擾。timber的本身設(shè)計(jì)就是一個(gè)log的框架,打印是交給開發(fā)者自定義的,所以我將timber的框架和logger的美觀實(shí)現(xiàn)進(jìn)行了結(jié)合。這當(dāng)然還要感謝logUtils的作者,讓log支持了object類型。

有朋友問,你為什么不自己實(shí)現(xiàn)log框架,而是依賴于timber做呢,這樣會(huì)不會(huì)太重?其實(shí)logger的1.1.6版本中,我確實(shí)是自己實(shí)現(xiàn)了所有的功能,沒有依賴于任何庫。當(dāng)我看到了timber后,我發(fā)現(xiàn)我做的工作和這個(gè)庫的重疊性太高了,而且它的設(shè)計(jì)也很值得學(xué)習(xí)。于是我直接依賴于它做了重構(gòu),我現(xiàn)在只關(guān)心log的美化和功能的擴(kuò)展,log分發(fā)的事情就交給timber了。

developer-kale@foxmail.com
微博:@天之界線2010

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,996評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,128評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,524評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,759評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,310評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,065評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,249評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,479評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評(píng)論 1 290
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,984評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,228評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,686評(píng)論 25 708
  • 5. 最佳實(shí)踐 好了終于要點(diǎn)講自己的東西了,有點(diǎn)小激動(dòng)。下面這些僅表示個(gè)人觀點(diǎn),非一定之規(guī),各位看官按需取用,有說...
    SnowDragonYY閱讀 2,398評(píng)論 4 36
  • 一.榜單介紹 排行榜包括四大類: 單一框架:僅提供路由、網(wǎng)絡(luò)層、UI層、通信層或其他單一功能的框架 混合開發(fā)框架:...
    偉子男閱讀 5,255評(píng)論 0 161
  • 附上原文作者連接:作者:金誠(chéng) 一.榜單介紹 排行榜包括四大類: 單一框架:僅提供路由、網(wǎng)絡(luò)層、UI層、通信層或其他...
    這個(gè)美嘉不姓陳閱讀 2,274評(píng)論 1 35
  • 我喜歡的,倉央嘉措 除了倉央嘉措,還有其他美好的一切
    孑孑啊閱讀 254評(píng)論 0 0