三天前基本上把數據庫表設計的文檔寫好,今天想到了一個問題,還要再加幾個表,一個是log表,用來記錄系統日志,另外再加幾個字典表,一些需要配置的數據但又不好放在像xml文件裡面的數據可以放在這些字典表裡面。
從今天開始就正式進入系統設計與編碼了,詳細設計文檔等系統做好後再補充了,因為一開始全部寫好不大現實,中間過程中會不斷地去迭代。現在的想法是每個模塊分別去實現,然後再分別記錄下來。
今天要寫的是日志模塊,因為在生產環境中,好的日志至於重要,系統運行時出現的任何問題可以通過日志記錄下來,對於發現與解決問題非常有幫助。因為日志是一個相對比較通用的模塊,所以先設計好如果寫日志模塊,之後再寫通用類模塊,再數據庫訪問層與用戶自定義控件,然後再數據實體與業務處理層,最後再寫用戶表現層。
因為此次不使用第二方控件,所以不考慮像log4net,微軟enterprise中好的日志控件,但是看了它們的代碼,總體思想都差不多,就是各種級別的日志信息應該以什麼樣的格式輸出到哪種介質中,也就是輸出源,像文本文件還是數據庫,還是控制台,還是系統日志郵件等等。基於它們的思想,把構思記錄下來:
日志最主要的就是一個日志器與附加器,像log4net中可以定義多個日志器,一個日志器可以附加多個輸出源,而日志倉庫就是如何存儲和管理日志器,過慮器如果過慮各種級別的日志,而layout就是如何顯示輸出的消息。
1、輸出源只包含文本文件,數據庫,系統日志和郵件。
2、日志級別分別為Fatal, Error, Warn, Info, Debug。
還是拿代碼為例子來講吧,首先定義一個日志器接口與日志操作接口ILog,日志器得先有一個名字,它的方法就是一個Log和IsEnabledFor,ILog包含的方法如下,用過log4net等日志控件的應該很熟悉。
日志器接口詳細代碼如下:
public interface ILogger { /// <summary> /// Gets the name of the logger. /// </summary> string Name { get; } /// <summary> /// This generic form is intended to be used by wrappers. /// </summary> void Log(LogCategory level, object message, Exception exception); bool IsEnabledFor(LogCategory level); }
然後再定義一個包裝接口
public interface ILoggerWrapper { ILogger Logger { get; } }
定義ILog接口,最後調用的都是在這裡定義的接口
public interface ILog : ILoggerWrapper { void Debug(object message); void Debug(object message, Exception exception); void DebugFormat(string format, params object[] args); void Info(object message); void Info(object message, Exception exception); void InfoFormat(string format, params object[] args); void Warn(object message); void Warn(object message, Exception exception); void WarnFormat(string format, params object[] args); void Error(object message); void Error(object message, Exception exception); void ErrorFormat(string format, params object[] args); void Fatal(object message); void Fatal(object message, Exception exception); void FatalFormat(string format, params object[] args); bool IsDebugEnabled { get; } bool IsInfoEnabled { get; } bool IsErrorEnabled { get; } bool IsWarnEnabled { get; } bool IsFatalEnabled { get; } }
日志器有了,可以附加器呢,也就是源出源,其實真正的任務都是委托這些具體的附加器去做的呢,為什麼log4net那麼強大,我想它的附加器如此之多也是一大原因吧,基本上我們能想到的它都想到了,我們沒有想到的它也想到了,下面就定義的幾個具體的附加器。
附加器如何跟前面的說的日志器關聯呢,20個附加器不可能都直接與日志器去關聯吧,所以定義一個所有附加器要實現的接口IAppender.
public interface IAppender { string Name { get; set; } void Close(); void DoAppender(LogCategory level, object message, Exception exception); }
拿文本文件為例,日志的輸出源就是文本文件,消息寫到這個介質上,下面是一個基類,然後子類就是繼承這個類實現Write方法去寫日志信息:
public class TextWriterAppender : TextWriter { private TextWriter m_writer; public TextWriter Writer { get { return m_writer; } set { m_writer = value; } } virtual public string Name { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } #region Public Methods /// <summary> /// Closes the writer and releases any system resources associated with the writer /// </summary> /// <remarks> /// <para> /// </para> /// </remarks> override public void Close() { m_writer.Close(); } /// <summary> /// Dispose this writer /// </summary> /// <param name="disposing">flag indicating if we are being disposed</param> /// <remarks> /// <para> /// Dispose this writer /// </para> /// </remarks> override protected void Dispose(bool disposing) { if (disposing) { ((IDisposable)m_writer).Dispose(); } } /// <summary> /// Flushes any buffered output /// </summary> /// <remarks> /// <para> /// Clears all buffers for the writer and causes any buffered data to be written /// to the underlying device /// </para> /// </remarks> override public void Flush() { m_writer.Flush(); } /// <summary> /// Writes a character to the wrapped TextWriter /// </summary> /// <param name="value">the value to write to the TextWriter</param> /// <remarks> /// <para> /// Writes a character to the wrapped TextWriter /// </para> /// </remarks> override public void Write(char value) { m_writer.Write(value); } /// <summary> /// Writes a character buffer to the wrapped TextWriter /// </summary> /// <param name="buffer">the data buffer</param> /// <param name="index">the start index</param> /// <param name="count">the number of characters to write</param> /// <remarks> /// <para> /// Writes a character buffer to the wrapped TextWriter /// </para> /// </remarks> override public void Write(char[] buffer, int index, int count) { m_writer.Write(buffer, index, count); } /// <summary> /// Writes a string to the wrapped TextWriter /// </summary> /// <param name="value">the value to write to the TextWriter</param> /// <remarks> /// <para> /// Writes a string to the wrapped TextWriter /// </para> /// </remarks> override public void Write(String value) { m_writer.Write(value); } public override Encoding Encoding { get { return m_writer.Encoding; } } #endregion }
日志器與附加器都有了,怎麼去連接它們了,最後我想還是用泛型比較靈活,定義如下:
public class LogFactory<L, A> : ILog where L : ILogger, new() where A : IAppender, new() { virtual public void Debug(object message) { #if DEBUG Logger.Log(m_levelDebug, message, null); #endif } virtual public void Debug(object message, Exception exception) { #if DEBUG Logger.Log(m_levelDebug, message, exception); #endif } virtual public void DebugFormat(string format, params object[] args) { #if DEBUG if (IsDebugEnabled) { Logger.Log(m_levelDebug, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } #endif } virtual public void Info(object message) { Logger.Log(LevelInfo, message, null); } virtual public void Info(object message, Exception exception) { Logger.Log(LevelInfo, message, exception); } virtual public void InfoFormat(string format, params object[] args) { if (IsInfoEnabled) { Logger.Log(LevelInfo, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public void Warn(object message) { Logger.Log(LevelWarn, message, null); } virtual public void Warn(object message, Exception exception) { Logger.Log(LevelWarn, message, exception); } virtual public void WarnFormat(string format, params object[] args) { if (IsWarnEnabled) { Logger.Log(LevelWarn, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public void Error(object message) { Logger.Log(LevelError, message, null); } virtual public void Error(object message, Exception exception) { Logger.Log(LevelError, message, exception); } virtual public void ErrorFormat(string format, params object[] args) { if (IsErrorEnabled) { Logger.Log(LevelError, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public void Fatal(object message) { Logger.Log(LevelFatal, message, null); } virtual public void Fatal(object message, Exception exception) { Logger.Log(LevelFatal, message, exception); } virtual public void FatalFormat(string format, params object[] args) { if (IsFatalEnabled) { Logger.Log(LevelFatal, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public bool IsDebugEnabled { get { return Logger.IsEnabledFor(m_levelDebug); } } virtual public bool IsInfoEnabled { get { return Logger.IsEnabledFor(m_levelInfo); } } virtual public bool IsErrorEnabled { get { return Logger.IsEnabledFor(m_levelError); } } virtual public bool IsWarnEnabled { get { return Logger.IsEnabledFor(m_levelWarn); } } virtual public bool IsFatalEnabled { get { return Logger.IsEnabledFor(m_levelFatal); } } private LogCategory m_levelDebug; public LogCategory LevelDebug { get { return LogCategory.Debug; } set { m_levelDebug = LogCategory.Debug; } } private LogCategory m_levelInfo; public LogCategory LevelInfo { get { return LogCategory.Info; } set { m_levelInfo = LogCategory.Info; } } private LogCategory m_levelWarn; public LogCategory LevelWarn { get { return LogCategory.Warn; } set { m_levelWarn = LogCategory.Warn; } } private LogCategory m_levelError; public LogCategory LevelError { get { return LogCategory.Error; } set { m_levelError = LogCategory.Error; } } private LogCategory m_levelFatal; public LogCategory LevelFatal { get { return LogCategory.Fatal; } set { m_levelFatal = LogCategory.Fatal; } } public ILogger Logger { get { return new L(); } } }
把上面的泛型類閉合一個日志類:
public class LogBase<A> : LogFactory<LogBase<A>, A>, ILogger where A : IAppender, new() { private LogCategory m_logLevel; public string Name { get { return "LogBase"; } } private A m_instance; public A Instance { get { if (m_instance == null) { m_instance = new A(); } return m_instance; } set { m_instance = value; } } public void Log(LogCategory level, object message, Exception exception) { Instance.DoAppender(level, message, exception); } public bool IsEnabledFor(LogCategory level) { switch (level) { case LogCategory.Fatal: LevelFatal = LogCategory.Fatal; break; case LogCategory.Error: LevelError = LogCategory.Error; break; case LogCategory.Warn: LevelWarn = LogCategory.Warn; break; case LogCategory.Debug: LevelDebug = LogCategory.Debug; break; case LogCategory.Info: LevelInfo = LogCategory.Info; break; default: m_logLevel = LogCategory.Info; break; } return true; } }
再關閉一個泛型參數:
public class TxtFileLog : LogBase<TxtFileLog>, IAppender { private string m_name; private FileLogWriter m_writer; public TxtFileLog() { if (m_writer == null) { m_writer = new FileLogWriter(); } } public FileLogWriter Writer { get { return m_writer; } set { m_writer = value; } } public new string Name { get { return m_name; } set { m_name = value; } } public void Close() { m_writer.Close(); } public void DoAppender(LogCategory level, object message, Exception exception) { m_writer.Write(Convert.ToString(message), level, (LogMessage)exception); }
上面的類實現了日志器與附加器的連接,然後就可以去客戶端驗證好不好用了:
ILog log = new TxtFileLog(); log.Debug("Are you OK??");
打印如下信息:
這只是第一步,後面還得寫數據庫與郵件附加器的輸出方法。寫一個好的日志器還真不容易。之後會把一些可以配置的東西放到配置文件裡面,
接下來就寫通用類與數據庫訪問層。
注:需要完整源碼的可以mark下,無償發到你郵箱