Timber 進(jìn)階用法

前言

本篇文章專注于 Timber 的進(jìn)階用法,包含一些稍微基礎(chǔ)的但有用的用法簡介,也包含一些自定義類實(shí)現(xiàn)不同的日志操作功能簡介。
如果還有不熟悉 Timber 的,可以先閱讀下我前面寫的一篇文章:Timber 源碼解析

  • tag用法簡介

tag() 的一般用法就是:Timber.tag("tag").d("message"),但是,前面我們分析源碼知道,Timbertag 是用 ThreadLocal 存儲(chǔ)的,也就是 tag 是區(qū)分線程的,更坑爹的是,tag 的設(shè)置是一次性行為,也就是說,當(dāng)你使用 getTag() 時(shí)候,獲取成功后,這個(gè) tag 也就被刪除了。所以,當(dāng)你想實(shí)現(xiàn)一個(gè)常量 tag 時(shí),你只能按如下的方法進(jìn)行輸出:

Timber.tag("hello").d("one");
Timber.tag("hello").d("two");
Timber.tag("haha").d("three");

吐槽:其實(shí)我不是很明白為什么 Jake Wharton 要把 tag 設(shè)置成線程區(qū)分,感覺沒什么意義;而且 tag 是設(shè)置給所有樹的,那么當(dāng)我們只想在一條線程進(jìn)行 tag 設(shè)置時(shí),其他線程的下一個(gè)日志輸出也會(huì)被強(qiáng)制輸出前面設(shè)置的 tag,而這卻是我們不想要的行為,總之,我感覺 Timbertag 設(shè)置是有待商榷的。當(dāng)然,我們可以在實(shí)現(xiàn)自定義Tree 時(shí),忽略父類 tag 設(shè)置行為,說到這里,我又要吐槽一下,因?yàn)?Tree.getTag() 是一個(gè)包級訪問權(quán)限,這也就是說我們沒辦法在其他包目錄對 getTag() 進(jìn)行復(fù)寫,改變它的行為 -_-,有點(diǎn)坑。

public static abstract class Tree {
    final ThreadLocal<String> explicitTag = new ThreadLocal<>();

    String getTag() {
      String tag = explicitTag.get();
      if (tag != null) {
        explicitTag.remove();
      }
      return tag;
    }

前面的路走不通,那我們只能另尋他路,辦法總比困難多嘛!通過查看源碼我們可以知道,tag() 函數(shù)設(shè)置的 tag 最終被傳遞給 Tree的純虛函數(shù) log()里面。

 protected abstract void log(int priority, String tag, String message, Throwable t);

所以,我們在實(shí)現(xiàn) Tree 子類時(shí),忽略這個(gè) tag 就相當(dāng)于忽略了父類的 tag 行為,然后,實(shí)現(xiàn)我們自己想要的 tag 行為就行了。

進(jìn)階用法:1. 常量 Tag 實(shí)現(xiàn)

比如,我們想實(shí)現(xiàn)一個(gè)常量 tag 功能,使我們能只進(jìn)行一個(gè) tag 設(shè)置,就能一直使用,其不區(qū)分線程,不區(qū)分類····,使我們調(diào)用能像下面代碼所示一樣:

Timber.plant(new ConstTagTree().setTag("ConstTag"));
Timber.v("watch the tag"); //tag == "ConstTag"
Timber.v("watch the tag haha");//tag == "ConstTag"
Timber.v("watch the tag hehe");//tag == "ConstTag"

實(shí)現(xiàn)這個(gè)代碼很簡單,子類提供一個(gè) setTag(String tag) 函數(shù),復(fù)寫父類 log() 函數(shù)時(shí),使用前面 setTag(String tag) 的值就行了。
詳細(xì)代碼可以查看:ConstTagTree
ConstTagTree 實(shí)現(xiàn)了常量 tag設(shè)置,如果沒有設(shè)置 tag,默認(rèn)以調(diào)用者類名作為 tag。
使用方法如下:

Timber.plant(new ConstTagTree().setTag("ConstTag")); //set const tag
//Timber.plant(new ConstTagTree()); //const tag not set,uses class name instead.
Timber.w("Application haha");    

進(jìn)階用法:2. Release版本調(diào)試:ReleaseTree

我們知道,Timber 默認(rèn)已經(jīng)為我們實(shí)現(xiàn)了一個(gè)調(diào)試版本的日志記錄類:DebugTree,但是 Release 版本的未提供。那么我們就自己實(shí)現(xiàn)一個(gè) Release版本的日志記錄類:ReleaseTree。
ReleaseTree 主要用于 Release 版本日志記錄,并且只記錄 warn,error,wtf 信息的日志。

public class ReleaseTree extends Timber.DebugTree {
    @Override
    protected boolean isLoggable(String tag, int priority) {
        return priority == Log.WARN
                || priority == Log.ERROR
                || priority == Log.ASSERT;
    }
}

好了,就是這么簡單,整個(gè) ReleaseTree 的完整代碼就是上面所示。
可以看到,ReleaseTree 是繼承了 DebugTree ,因此也就擁有了不設(shè)置 tag 就自動(dòng)使用調(diào)用者類名作為 tag 的功能,不過,Release 集成 Debug 這個(gè)繼承關(guān)系其實(shí)給人的感覺是不太友好的,這兩者沒有繼承關(guān)系這個(gè)概念(這里使用繼承僅僅是為了使用到自動(dòng)類名 tag 這個(gè)功能,與繼承沒有直接關(guān)系),但是,還是上面那個(gè)問題,由于 TimberTree.getTag() 是包級權(quán)限,子類沒辦法獲取,也就是沒辦法復(fù)寫 tag 的行為,那我們只能采用繼承的方法獲取同個(gè)包級下的 DebugTreetag 功能了。
ReleaseTree 的代碼詳見:ReleaseTree

進(jìn)階用法:3. 文件日志記錄功能:FileTree

很多情況下,當(dāng)我們的app運(yùn)行在裸機(jī)上時(shí),我們沒辦法直接獲取到app運(yùn)行日志信息,那么,將app運(yùn)行日志信息保存到文件中或許就是一個(gè)良好的解決方法,通過查看文件日志,我們就可以看出app運(yùn)行中哪個(gè)方面出現(xiàn)了問題,這在項(xiàng)目中應(yīng)該是非常有用處的。
那下面我們就來實(shí)現(xiàn)一個(gè)簡單的 FileTreeFileTree
FileTree 代碼相對較多,這里就不進(jìn)行展示了,我們這里就看下使用方法:

String path = Environment.getExternalStorageDirectory().getAbsolutePath();
Timber.plant(new FileTree().name("FileTree.txt").storeAt(path));
Timber.v("log in file"); //this will be store in file

進(jìn)階用法:4. 集成其他日志框架

上面我們所介紹的都是通過自己直接或間接地繼承 Tree 來實(shí)現(xiàn)我們所需要的日志操作功能,如果你覺得自己去實(shí)現(xiàn)這些日志操作太麻煩,一件值得高興的事,由于 Timber 其實(shí)是一個(gè)封裝日志操作的一個(gè)框架,不涉及具體功能的實(shí)現(xiàn),所以,基于這樣的架構(gòu)設(shè)計(jì),我們就可以在 Github 上找到一些具體的執(zhí)行日志操作功能的項(xiàng)目,然后種植到 Timber 中就行了。
這里,我隨便找一份日志操作項(xiàng)目:Logger
很湊巧的是,該庫本身就考慮到集成到 Timber 中來,其 ReadMe.md 中就給我們直接指出了集成方法:

// Set methodOffset to 5 in order to hide internal method calls
Timber.plant(new Timber.DebugTree() {
  @Override protected void log(int priority, String tag, String message, Throwable t) {
    Logger.log(priority, tag, message, t);
  }
});

進(jìn)階用法:5. 多 Tag 功能實(shí)現(xiàn):MultiTagTree

有些時(shí)候,我們希望日志輸出既包含類名,也包含我們自定義的其他 tag,這樣做的好處是一方面我們可以只關(guān)注部分日志(通過類名過濾),另一方面,我們也希望能查看一下完整的日志(通過自定義 tag 過濾)。
我這邊的實(shí)現(xiàn)思路是將自定義 tag 插入到 原來的 tag 前面,并以分隔符“-”進(jìn)行分割。

public class MultiTagTree extends Timber.DebugTree {
    private static final String SEPARATOR = "-";
    private List<String> mTags = new CopyOnWriteArrayList<>();

    @Override
    protected void log(int priority, String tag, String message, Throwable t) {
        String newTag = produceTag(tag);
        super.log(priority, newTag, message, t);
    }

    //override this method to produce your own multi tag
    protected String produceTag(String originTag) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0, lenth = mTags.size(); i < lenth; ++i) {
            builder.append(mTags.get(i))
                    .append(i != lenth - 1 ? SEPARATOR :
                            (originTag == null ? "" : SEPARATOR + originTag));
        }
        return builder.toString();
    }
···
詳細(xì)代碼可查看:[MultiTagTree]
···
}
Timber.plant(new MultiTagTree().addTag("Whyn111")); //then the tag is like "Whyn111-MainActivity"

這樣,我們就可以實(shí)現(xiàn)多 tag 功能。

最后,附上本章源碼:TimberExtendedDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容