Unity應用架構設計(13)——日誌元件的實施
對於應用程式而言,日誌是非常重要的功能,通過日誌,我們可以跟蹤應用程式的資料狀態,記錄Crash的日誌可以幫助我們分析應用程式崩潰的原因,我們甚至可以通過日誌來進行效能的監控。總之,日誌的好處很多,特別是對Release之後的線上版本進行異常的跟蹤。
日誌儲存的分類
在平常開發時,我們通常喜歡在Debug模式下進行除錯,通過斷點,可以跟蹤資料的變化。除了除錯,另一種直觀的方式是使用控制檯輸出,比如Java的system.out.println()
,.NET的Console.WriteLine()
,Swift的print()
等等。在Untiy中,為我們提供了Debug.Log()
方式來記錄。
而對於線上的版本,上述兩種除錯都不行,那我們怎麼來跟蹤資料呢?
從日誌的儲存分類上來看,可以分為四類:控制檯,檔案系統,資料庫,第三方平臺
- 控制檯:本地開發時使用,記錄資料和跟蹤執行過程,方便直觀
- 檔案系統:可以是一些使用者行為性的日誌,這些檔案可以被用來監控執行時間,進行效能的分析,如果使用者同意,則將這些日誌傳到伺服器上
- 資料庫:記錄了一些異常日誌,也就是Catch了之後的行為,每次使用者登入時,傳到伺服器,幫助分析原因
- 第三方平臺:比如友盟等,當應用閃退時,Crash原因會記錄在友盟中,可以通過DashBoard檢視
日誌元件的設計
為了可以更加靈活的跟蹤線上的變化,可以使用第三方的Analysis,也可以自建日誌元件。我偏向於混合使用,所以接下來,談談一個日誌元件的基本設計理念,如下圖所示:
從上圖可以看出,整個入口由工廠LogFactory
來建立LogStrategy
子類例項,LogStrategy
是個抽象的模板類,定義了公共的處理方法,但並不知道怎樣寫日誌(比如是寫入到資料庫呢還是到檔案),寫日誌的行為交給子類去完成。
日誌元件的實施
有了日誌元件的設計圖,接下來就是將理念落實到行動,讓我們來實現它吧!
LogFactory是一個簡單工廠,封裝建立LogStrategy物件的程式碼。
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
內部定義了一個字典,Key為LogStrategy子類的類名,Value為具體的LogStrategy物件。通過一個公共介面Resolve<T>
來獲取相關物件。
使用字典比switch..case
更直觀,也更加容易擴充套件其他選項。更重要的是,不會對公共介面Resolve<T>
進行修改。
LogStrategy是一個抽象類,即模板類。
它定義了一個公共的API,即Log
。在方法Log
中,定義了一些對內容的公共操作,因為對於日誌來說,不管是記錄在資料庫還是檔案系統,都將對內容拼接上裝置型別、裝置名稱、作業系統、建立時間等基本資訊。
同時還定義了一個抽象方法RecordMessage
,對於需要寫入的型別(檔案系統Or資料庫Or控制檯)延遲到子類決定。
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();
}
//抽象方法,交由子類實現
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();
}
模板方法模式:在一個方法中定義演算法的骨架,而將一些步驟延遲到子類。模板方法使得子類可以在不改變演算法的結構下,重新定義演算法中的某些步驟。
當在控制檯Debug時,我們其實不需要裝置型別,裝置名稱等資訊,故公共介面Log
提供了一個開關verbose
來開啟是否需要詳細資訊,預設為false,即關閉狀態。
繼承LogStrategy,建立自定義的日誌策略
比如實現FileLogStrategy,除了override了 RecordMessage
方法之外,還需要提供一個實現了IContentWriter
介面的類,你可以直接在RecordMessage
方法中寫入日誌,但可能有一些公共的操作,比如在非同步執行緒,批量將10條資料寫到檔案或者資料庫中,所以提供一個IContentWriter
更加容易擴充套件。
public class FileLogStrategy:LogStrategy
{
public FileLogStrategy()
{
SetContentWriter();
}
protected sealed override void SetContentWriter()
{
Writer = new FileContentWriter();
}
protected override void RecordMessage(string message)
{
Writer.Write(message);
}
}
建立一個
BaseContentWriter
,提供了公共的寫入方法,比如為了提高效能,檔案的IO並不是馬上寫入檔案,而是批量Flush。同樣資料庫記錄日誌也是一樣,像Unit Of Work那樣,批量向資料庫寫入資料,提高它的吞吐率。
根據需求使用不同的日誌類
LogFactory.Instance.Resolve<FileLogStrategy>().Log("Welcome");
小結
不同於伺服器端的日誌元件,比如Log4J,只需要將日誌寫在本地檔案系統中,客戶端的日誌相對來說複雜點,因為記錄的日誌是發生在使用者的客戶端,所以你必須要想辦法把日誌傳到伺服器,比如一些Crash的異常。既然要把日誌發回來,在應用閃退時,必須能夠持久化到本地,故我們會將日誌寫到檔案系統或者資料庫,然後在合適的時候將日誌傳送到伺服器進行分析。當然,你也可以使用第三方的服務,比如友盟或者 Unity Analytics 來分析資料。
原始碼託管在Github上,點選此瞭解