對(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ì)理念,如下圖所示:
從上圖可以看出,整個(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)擊此了解
本博客為木宛城主原創(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