對於應用程序而言,日志是非常重要的功能,通過日志,我們可以跟蹤應用程序的數據狀態,記錄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上,點擊此了解
我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan
