1. 程式人生 > >Unity應用架構設計(13)——日誌元件的實施

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上,點選此瞭解