對(duì)于應(yīng)用程序而言,日志是非常重要的功能,通過(guò)日志,我們可以跟蹤應(yīng)用程序的數(shù)據(jù)狀態(tài),記錄Crash的日志可以幫助我們分析應(yīng)用程序崩潰的原因,我們甚至可以通過(guò)日志來(lái)進(jìn)行性能的監(jiān)控??傊?,日志的好處很多,特別是對(duì)Release之后的線上版本進(jìn)行異常的跟蹤。

回到頂部

日志存儲(chǔ)的分類

在平常開(kāi)發(fā)時(shí),我們通常喜歡在Debug模式下進(jìn)行調(diào)試,通過(guò)斷點(diǎn),可以跟蹤數(shù)據(jù)的變化。除了調(diào)試,另一種直觀的方式是使用控制臺(tái)輸出,比如Java的system.out.println(),.NET的Console.WriteLine(),Swift的print()等等。在Untiy中,為我們提供了Debug.Log()方式來(lái)記錄。

而對(duì)于線上的版本,上述兩種調(diào)試都不行,那我們?cè)趺磥?lái)跟蹤數(shù)據(jù)呢?

從日志的存儲(chǔ)分類上來(lái)看,可以分為四類:控制臺(tái),文件系統(tǒng),數(shù)據(jù)庫(kù),第三方平臺(tái)

  • 控制臺(tái):本地開(kāi)發(fā)時(shí)使用,記錄數(shù)據(jù)和跟蹤執(zhí)行過(guò)程,方便直觀

  • 文件系統(tǒng):可以是一些用戶行為性的日志,這些文件可以被用來(lái)監(jiān)控執(zhí)行時(shí)間,進(jìn)行性能的分析,如果用戶同意,則將這些日志傳到服務(wù)器上

  • 數(shù)據(jù)庫(kù):記錄了一些異常日志,也就是Catch了之后的行為,每次用戶登錄時(shí),傳到服務(wù)器,幫助分析原因

  • 第三方平臺(tái):比如友盟等,當(dāng)應(yīng)用閃退時(shí),Crash原因會(huì)記錄在友盟中,可以通過(guò)DashBoard查看

回到頂部

日志組件的設(shè)計(jì)

為了可以更加靈活的跟蹤線上的變化,可以使用第三方的Analysis,也可以自建日志組件。我偏向于混合使用,所以接下來(lái),談?wù)勔粋€(gè)日志組件的基本設(shè)計(jì)理念,如下圖所示:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

從上圖可以看出,整個(gè)入口由工廠LogFactory來(lái)創(chuàng)建LogStrategy子類實(shí)例,LogStrategy是個(gè)抽象的模板類,定義了公共的處理方法,但并不知道怎樣寫(xiě)日志(比如是寫(xiě)入到數(shù)據(jù)庫(kù)呢還是到文件),寫(xiě)日志的行為交給子類去完成。

回到頂部

日志組件的實(shí)施

有了日志組件的設(shè)計(jì)圖,接下來(lái)就是將理念落實(shí)到行動(dòng),讓我們來(lái)實(shí)現(xiàn)它吧!

LogFactory是一個(gè)簡(jiǎn)單工廠,封裝創(chuàng)建LogStrategy對(duì)象的代碼。

public class LogFactory{    public static LogFactory Instance=new LogFactory();    private LogFactory(){}    private readonly Dictionary<string,LogStrategy> _strategies=new Dictionary<string, LogStrategy>()
    {
        {typeof(ConsoleLogStrategy).Name,new ConsoleLogStrategy() },
        {typeof(FileLogStrategy).Name,new FileLogStrategy() },
        {typeof(DatabaseLogStrategy).Name,new DatabaseLogStrategy() }
    }; 
    public LogStrategy Resolve<T>() where T:LogStrategy
    {        return _strategies[typeof(T).Name];
    }
}

LogFactory內(nèi)部定義了一個(gè)字典,Key為L(zhǎng)ogStrategy子類的類名,Value為具體的LogStrategy對(duì)象。通過(guò)一個(gè)公共接口Resolve<T>來(lái)獲取相關(guān)對(duì)象。

使用字典比switch..case更直觀,也更加容易擴(kuò)展其他選項(xiàng)。更重要的是,不會(huì)對(duì)公共接口Resolve<T>進(jìn)行修改。

LogStrategy是一個(gè)抽象類,即模板類。

它定義了一個(gè)公共的API,即Log。在方法Log中,定義了一些對(duì)內(nèi)容的公共操作,因?yàn)閷?duì)于日志來(lái)說(shuō),不管是記錄在數(shù)據(jù)庫(kù)還是文件系統(tǒng),都將對(duì)內(nèi)容拼接上設(shè)備類型、設(shè)備名稱、操作系統(tǒng)、創(chuàng)建時(shí)間等基本信息。

同時(shí)還定義了一個(gè)抽象方法RecordMessage,對(duì)于需要寫(xiě)入的類型(文件系統(tǒng)Or數(shù)據(jù)庫(kù)Or控制臺(tái))延遲到子類決定。

public abstract class LogStrategy{    private readonly StringBuilder _messageBuilder=new StringBuilder();    protected IContentWriter Writer { get; set; }    /// <summary>
    ///     模板方法
    /// </summary>
    protected abstract void RecordMessage(string message);    protected abstract void SetContentWriter();    /// <summary>
    ///     公共的API
    /// </summary>
    public void Log(string message,bool verbose=false)    {        if (verbose)
        {            //公共方法
            RecordDateTime();
            RecordDeviceModel();
            RecordDeviceName();
            RecordOperatingSystem();
        }        //抽象方法,交由子類實(shí)現(xiàn)
        RecordMessage(_messageBuilder.AppendLine(string.Format("Message:{0}", message)).ToString());
    }    private void RecordDateTime()    {
        _messageBuilder.AppendLine(string.Format("DateTime:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
    }    private void RecordDeviceModel()    {
        _messageBuilder.AppendLine(string.Format("Device Model:{0}",SystemInfo.deviceModel));
    }    private void RecordDeviceName()    {
        _messageBuilder.AppendLine(string.Format("Device Name:{0}", SystemInfo.deviceName));
    }    private void RecordOperatingSystem()    {
        _messageBuilder
            .AppendLine(string.Format("Operating System:{0}", SystemInfo.operatingSystem))
            .AppendLine();
    }

模板方法模式:在一個(gè)方法中定義算法的骨架,而將一些步驟延遲到子類。模板方法使得子類可以在不改變算法的結(jié)構(gòu)下,重新定義算法中的某些步驟。

當(dāng)在控制臺(tái)Debug時(shí),我們其實(shí)不需要設(shè)備類型,設(shè)備名稱等信息,故公共接口Log提供了一個(gè)開(kāi)關(guān)verbose來(lái)開(kāi)啟是否需要詳細(xì)信息,默認(rèn)為false,即關(guān)閉狀態(tài)。

繼承LogStrategy,創(chuàng)建自定義的日志策略

比如實(shí)現(xiàn)FileLogStrategy,除了override了 RecordMessage方法之外,還需要提供一個(gè)實(shí)現(xiàn)了IContentWriter接口的類,你可以直接在RecordMessage方法中寫(xiě)入日志,但可能有一些公共的操作,比如在異步線程,批量將10條數(shù)據(jù)寫(xiě)到文件或者數(shù)據(jù)庫(kù)中,所以提供一個(gè)IContentWriter更加容易擴(kuò)展。

public class FileLogStrategy:LogStrategy{    public FileLogStrategy()    {
        SetContentWriter();
    }    protected sealed override void SetContentWriter()    {
        Writer = new FileContentWriter(); 
    }    protected override void RecordMessage(string message)    {
        Writer.Write(message);
    }

}

創(chuàng)建一個(gè)BaseContentWriter,提供了公共的寫(xiě)入方法,比如為了提高性能,文件的IO并不是馬上寫(xiě)入文件,而是批量Flush。同樣數(shù)據(jù)庫(kù)記錄日志也是一樣,像Unit Of Work那樣,批量向數(shù)據(jù)庫(kù)寫(xiě)入數(shù)據(jù),提高它的吞吐率。

根據(jù)需求使用不同的日志類

LogFactory.Instance.Resolve<FileLogStrategy>().Log("Welcome");

回到頂部

小結(jié)

不同于服務(wù)器端的日志組件,比如Log4J,只需要將日志寫(xiě)在本地文件系統(tǒng)中,客戶端的日志相對(duì)來(lái)說(shuō)復(fù)雜點(diǎn),因?yàn)橛涗浀娜罩臼前l(fā)生在用戶的客戶端,所以你必須要想辦法把日志傳到服務(wù)器,比如一些Crash的異常。既然要把日志發(fā)回來(lái),在應(yīng)用閃退時(shí),必須能夠持久化到本地,故我們會(huì)將日志寫(xiě)到文件系統(tǒng)或者數(shù)據(jù)庫(kù),然后在合適的時(shí)候?qū)⑷罩景l(fā)送到服務(wù)器進(jìn)行分析。當(dāng)然,你也可以使用第三方的服務(wù),比如友盟或者 Unity Analytics 來(lái)分析數(shù)據(jù)。
源代碼托管在Github上,點(diǎn)擊此了解

Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

本博客為木宛城主原創(chuàng),基于Creative Commons Attribution 2.5 China Mainland License發(fā)布,歡迎轉(zhuǎn)載,演繹或用于商業(yè)目的,但是必須保留本文的署名木宛城主(包含鏈接)。如您有任何疑問(wèn)或者授權(quán)方面的協(xié)商,請(qǐng)給我留言。

http://www.cnblogs.com/OceanEyes/p/log_strategy.html