對於應用程式而言,日誌是非常重要的功能,通過日誌,我們可以跟蹤應用程式的數據狀態,記錄Crash的日誌可以幫助我們分析應用程式崩潰的原因,我們甚至可以通過日誌來進行性能的監控。總之,日誌的好處很多,特別是對Release之後的線上版本進行異常的跟蹤。 日誌存儲的分類 在平常開發時,我們通常喜歡在De ...
對於應用程式而言,日誌是非常重要的功能,通過日誌,我們可以跟蹤應用程式的數據狀態,記錄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上,點擊此瞭解