【開源】OSharp框架解說系列(6.1):日志系統設計


OSharp是什么?

  OSharp是個快速開發框架,但不是一個大而全的包羅萬象的框架,嚴格的說,OSharp中什么都沒有實現。與其他大而全的框架最大的不同點,就是OSharp只做抽象封裝,不做實現。依賴注入、ORM、對象映射、日志、緩存等等功能,都只定義了一套最基礎最通用的抽象封裝,提供了一套統一的API、約定與規則,並定義了部分執行流程,主要是讓項目在一定的規范下進行開發。所有的功能實現端,都是通過現有的成熟的第三方組件來實現的,除了EntityFramework之外,所有的第三方實現都可以輕松的替換成另一種第三方實現,OSharp框架正是要起隔離作用,保證這種變更不會對業務代碼造成影響,使用統一的API來進行業務實現,解除與第三方實現的耦合,保持業務代碼的規范與穩定。

本文已同步到系列目錄:OSharp快速開發框架解說系列

前言

  日志記錄對於一個系統而言,重要性不言而喻。日志記錄功能在系統開發階段,往往容易被忽略。因為開發階段,程序可以調試,可以反復的運行以查找問題。但在系統進入正常的運行維護階段,特別是在進行審計統計的時候,追蹤問題的時候,在追溯責任的時候,在系統出錯的時候等等場景中,日志記錄才會顯示出它不可替代的作用。記錄的日志,平時看似累贅,只有在需要的時候,才會拍大腿后悔當初為什么不把日志記錄得詳細些。

  日志系統,是一個非常基礎的系統,但由於需求的復雜性,各個場景需要的日志分類,來源,輸出方式各有不同,日志系統又是一個相對復雜的系統。下面我們就來解說一下,OSharp開發框架的日志系統設計中,怎樣來應對這些復雜性。

系統架構設計圖

  OSharp 開發框架的日志部分定義了一套基礎的通用的日志記錄協議,來創建並管理日志記錄對象,並處理日志的輸入邏輯,沒有日志的輸出實現,日志的輸出完全由第三方日志組件來完成。基本架構圖如下圖所示:

   

  項目代碼組成:

  

  日志記錄的過程按如下步驟進行:

  1. 日志管理器 LogManager 中定義了兩個倉庫:日志記錄者倉庫 ConcurrentDictionary<string, ILogger> 與 日志輸出者適配器倉庫 ICollection<ILoggerAdapter>
  2. 具體的日志輸出方式實現相應的日志輸出適配器 ILoggerAdapter,並添加到 LogManager 的日志輸出適配器倉庫中
  3. 日志記錄點使用名稱通過 LogManager 創建(提取)日志記錄者 Logger,並存回到 LogManager 的倉庫中等下次備用
  4. 日志輸出者調用 LogManager 中的所有 ILoggerAdapter 適配器(例如 Log4NetLoggerAdapter)輸出日志
  5. 日志適配器 ILoggerAdapter 調用具體的日志輸出者 LogBase(例如 Log4NetLog)進行日志的輸出

  OSharp 的日志系統在設計上有如下特點:

  1. 整個OSharp 日志系統只負責收集日志信息,並不關心日志的輸出行為
  2. 具體的日志輸出行為由各個輸出方案實現 日志輸出適配器 來完成
  3. 一條日志信息,可以有一個或多個日志輸出途徑

核心設計

  下面我們來一步一步解析上圖的設計思路。

日志記錄行為約定

   在OSharp的設計中,為了滿足各種應用場景對日志輸出級別的控制,定義了 8 個日志輸出的級別:

  1. All:輸出所有級別的日志
  2. Trace:輸出跟蹤級別的日志
  3. Debug:輸出調試級別的日志
  4. Info:輸出消息級別的日志
  5. Warn:輸出警告級別的日志
  6. Error:輸出錯誤級別的日志
  7. Fatal:輸出嚴重錯誤級別的日志
  8. Off:關閉所有日志級別,不輸出日志
 1 /// <summary>
 2 /// 表示日志輸出級別的枚舉
 3 /// </summary>
 4 public enum LogLevel
 5 {
 6     /// <summary>
 7     /// 輸出所有級別的日志
 8     /// </summary>
 9     All = 0,
10 
11     /// <summary>
12     /// 表示跟蹤的日志級別
13     /// </summary>
14     Trace = 1,
15 
16     /// <summary>
17     /// 表示調試的日志級別
18     /// </summary>
19     Debug = 2,
20 
21     /// <summary>
22     /// 表示消息的日志級別
23     /// </summary>
24     Info = 3,
25 
26     /// <summary>
27     /// 表示警告的日志級別
28     /// </summary>
29     Warn = 4,
30 
31     /// <summary>
32     /// 表示錯誤的日志級別
33     /// </summary>
34     Error = 5,
35 
36     /// <summary>
37     /// 表示嚴重錯誤的日志級別
38     /// </summary>
39     Fatal = 6,
40 
41     /// <summary>
42     /// 關閉所有日志,不輸出日志
43     /// </summary>
44     Off = 7
45 }

  對於各個日志級別,日志系統都有相應的日志記錄行為規范。各個級別都支持 泛型、格式化字符串 的輸出,對於錯誤與嚴重錯誤的級別,支持 Exception 異常信息的支持。

  日志記錄行為定義為 ILogger 接口:

  1 /// <summary>
  2 /// 定義日志記錄行為
  3 /// </summary>
  4 public interface ILogger
  5 {
  6     #region 方法
  7 
  8     /// <summary>
  9     /// 寫入<see cref="LogLevel.Trace"/>日志消息
 10     /// </summary>
 11     /// <param name="message">日志消息</param>
 12     void Trace<T>(T message);
 13 
 14     /// <summary>
 15     /// 寫入<see cref="LogLevel.Trace"/>格式化日志消息
 16     /// </summary>
 17     /// <param name="format">日志消息格式</param>
 18     /// <param name="args">格式化參數</param>
 19     void Trace(string format, params object[] args);
 20 
 21     /// <summary>
 22     /// 寫入<see cref="LogLevel.Debug"/>日志消息
 23     /// </summary>
 24     /// <param name="message">日志消息</param>
 25     void Debug<T>(T message);
 26 
 27     /// <summary>
 28     /// 寫入<see cref="LogLevel.Debug"/>格式化日志消息
 29     /// </summary>
 30     /// <param name="format">日志消息格式</param>
 31     /// <param name="args">格式化參數</param>
 32     void Debug(string format, params object[] args);
 33 
 34     /// <summary>
 35     /// 寫入<see cref="LogLevel.Info"/>日志消息
 36     /// </summary>
 37     /// <param name="message">日志消息</param>
 38     void Info<T>(T message);
 39 
 40     /// <summary>
 41     /// 寫入<see cref="LogLevel.Info"/>格式化日志消息
 42     /// </summary>
 43     /// <param name="format">日志消息格式</param>
 44     /// <param name="args">格式化參數</param>
 45     void Info(string format, params object[] args);
 46 
 47     /// <summary>
 48     /// 寫入<see cref="LogLevel.Warn"/>日志消息
 49     /// </summary>
 50     /// <param name="message">日志消息</param>
 51     void Warn<T>(T message);
 52 
 53     /// <summary>
 54     /// 寫入<see cref="LogLevel.Warn"/>格式化日志消息
 55     /// </summary>
 56     /// <param name="format">日志消息格式</param>
 57     /// <param name="args">格式化參數</param>
 58     void Warn(string format, params object[] args);
 59 
 60     /// <summary>
 61     /// 寫入<see cref="LogLevel.Error"/>日志消息
 62     /// </summary>
 63     /// <param name="message">日志消息</param>
 64     void Error<T>(T message);
 65 
 66     /// <summary>
 67     /// 寫入<see cref="LogLevel.Error"/>格式化日志消息
 68     /// </summary>
 69     /// <param name="format">日志消息格式</param>
 70     /// <param name="args">格式化參數</param>
 71     void Error(string format, params object[] args);
 72 
 73     /// <summary>
 74     /// 寫入<see cref="LogLevel.Error"/>日志消息,並記錄異常
 75     /// </summary>
 76     /// <param name="message">日志消息</param>
 77     /// <param name="exception">異常</param>
 78     void Error<T>(T message, Exception exception);
 79 
 80     /// <summary>
 81     /// 寫入<see cref="LogLevel.Error"/>格式化日志消息,並記錄異常
 82     /// </summary>
 83     /// <param name="format">日志消息格式</param>
 84     /// <param name="exception">異常</param>
 85     /// <param name="args">格式化參數</param>
 86     void Error(string format, Exception exception, params object[] args);
 87 
 88     /// <summary>
 89     /// 寫入<see cref="LogLevel.Fatal"/>日志消息
 90     /// </summary>
 91     /// <param name="message">日志消息</param>
 92     void Fatal<T>(T message);
 93 
 94     /// <summary>
 95     /// 寫入<see cref="LogLevel.Fatal"/>格式化日志消息
 96     /// </summary>
 97     /// <param name="format">日志消息格式</param>
 98     /// <param name="args">格式化參數</param>
 99     void Fatal(string format, params object[] args);
100 
101     /// <summary>
102     /// 寫入<see cref="LogLevel.Fatal"/>日志消息,並記錄異常
103     /// </summary>
104     /// <param name="message">日志消息</param>
105     /// <param name="exception">異常</param>
106     void Fatal<T>(T message, Exception exception);
107 
108     /// <summary>
109     /// 寫入<see cref="LogLevel.Fatal"/>格式化日志消息,並記錄異常
110     /// </summary>
111     /// <param name="format">日志消息格式</param>
112     /// <param name="exception">異常</param>
113     /// <param name="args">格式化參數</param>
114     void Fatal(string format, Exception exception, params object[] args);
115 
116     #endregion
117 }

日志輸出適配設計

日志輸出適配器

   不同的需求有不同的日志輸出需求,通常我們會遇到如下的場景:

  1. 操作類日志,常常與業務相關,需要輸出到數據庫中,便於查看與追溯
  2. 實時監控系統中,日志需要實時輸出到控制台,便於即時的反饋給觀察者
  3. 對於錯誤異常類的信息,基查看人員通常是開發人員與運維人員,與業務相關度不大,用文本文件按天記錄即可
  4. ...

  面對眾多的需求場景,日志信息的輸出形式就需靈活多變,必須能靈活替換或添加一個或多個輸出行為。為應對這些需求,並充分把現有的輸出行為利用上,OSharp 的日志系統的日志輸出行為是靈活的,主要通過定義一系列的日志輸出適配器來完成。

  日志輸出適配器 ILoggerAdapter 定義如下,主要是定義獲取 日志輸出者適配對象 ILog 的創建行為:

 1 /// <summary>
 2 /// 由指定類型獲取<see cref="ILog"/>日志實例
 3 /// </summary>
 4 /// <param name="type">指定類型</param>
 5 /// <returns></returns>
 6 ILog GetLogger(Type type);
 7 
 8 /// <summary>
 9 /// 由指定名稱獲取<see cref="ILog"/>日志實例
10 /// </summary>
11 /// <param name="name">指定名稱</param>
12 /// <returns></returns>
13 ILog GetLogger(string name);

   每一個日志輸出適配器,都對應着一系列具體的日志輸出實現技術的日志輸出適配對象(如Log4Net中的 log4net.Core.ILogger 類的對象),適配器需要自行管理這些對象的創建與銷毀行為,並把創建的日志輸出適配對象按名稱緩存起來。OSharp中通過定義一個通用適配器基類 LoggerAdapterBase 來規范這些行為。

 1 /// <summary>
 2 /// 按名稱緩存的日志輸出適配器基類,用於創建並管理指定類型的日志輸出者實例
 3 /// </summary>
 4 public abstract class LoggerAdapterBase : ILoggerAdapter
 5 {
 6     private readonly ConcurrentDictionary<string, ILog> _cacheLoggers;
 7 
 8     /// <summary>
 9     /// 初始化一個<see cref="LoggerAdapterBase"/>類型的新實例
10     /// </summary>
11     protected LoggerAdapterBase()
12     {
13         _cacheLoggers = new ConcurrentDictionary<string, ILog>();
14     }
15 
16     #region Implementation of ILoggerFactoryAdapter
17 
18     /// <summary>
19     /// 由指定類型獲取<see cref="ILog"/>日志實例
20     /// </summary>
21     /// <param name="type">指定類型</param>
22     /// <returns></returns>
23     public ILog GetLogger(Type type)
24     {
25         type.CheckNotNull("type");
26         return GetLoggerInternal(type.FullName);
27     }
28 
29     /// <summary>
30     /// 由指定名稱獲取<see cref="ILog"/>日志實例
31     /// </summary>
32     /// <param name="name">指定名稱</param>
33     /// <returns></returns>
34     public ILog GetLogger(string name)
35     {
36         name.CheckNotNullOrEmpty("name");
37         return GetLoggerInternal(name);
38     }
39 
40     #endregion
41 
42     /// <summary>
43     /// 創建指定名稱的緩存實例
44     /// </summary>
45     /// <param name="name">指定名稱</param>
46     /// <returns></returns>
47     protected abstract ILog CreateLogger(string name);
48 
49     /// <summary>
50     /// 清除緩存中的日志實例
51     /// </summary>
52     protected virtual void ClearLoggerCache()
53     {
54         _cacheLoggers.Clear();
55     }
56 
57     private ILog GetLoggerInternal(string name)
58     {
59         ILog log;
60         if (_cacheLoggers.TryGetValue(name, out log))
61         {
62             return log;
63         }
64         log = CreateLogger(name);
65         if (log == null)
66         {
67             throw new NotSupportedException(Resources.Logging_CreateLogInstanceReturnNull.FormatWith(name, GetType().FullName));
68         }
69         _cacheLoggers[name] = log;
70         return log;
71     }
72 }

  適配器基類中,定義了一個 ConcurrentDictionary<string, ILog> 倉庫通過名稱來管理適配之后的 ILog 對象,並向派生類開放了一個 protected abstract ILog CreateLogger(string name); 來獲取 ILog 對象的具體適配實現。

日志輸出者適配對象

  日志輸出者適配對象 ILog 接口派生自 ILogger ,添加了日志輸出的許可屬性,用於定義具體的日志輸出者對象:

 1 /// <summary>
 2 /// 表示日志實例的接口
 3 /// </summary>
 4 public interface ILog : ILogger
 5 {
 6     #region 屬性
 7 
 8     /// <summary>
 9     /// 獲取 是否允許<see cref="LogLevel.Trace"/>級別的日志
10     /// </summary>
11     bool IsTraceEnabled { get; }
12 
13     /// <summary>
14     /// 獲取 是否允許<see cref="LogLevel.Debug"/>級別的日志
15     /// </summary>
16     bool IsDebugEnabled { get; }
17 
18     /// <summary>
19     /// 獲取 是否允許<see cref="LogLevel.Info"/>級別的日志
20     /// </summary>
21     bool IsInfoEnabled { get; }
22 
23     /// <summary>
24     /// 獲取 是否允許<see cref="LogLevel.Warn"/>級別的日志
25     /// </summary>
26     bool IsWarnEnabled { get; }
27 
28     /// <summary>
29     /// 獲取 是否允許<see cref="LogLevel.Error"/>級別的日志
30     /// </summary>
31     bool IsErrorEnabled { get; }
32 
33     /// <summary>
34     /// 獲取 是否允許<see cref="LogLevel.Fatal"/>級別的日志
35     /// </summary>
36     bool IsFatalEnabled { get; }
37 
38     #endregion
39 
40 }

   日志輸出者適配類,主要是使用現有的日志輸出實現類(如 log4net.Core.ILogger )去實現具體的日志輸出行為,在下面這個 日志輸出者適配基類 LogBase 中,主要是通過重寫 Write 方法來實現。

  1 /// <summary>
  2 /// 日志輸出者適配基類,用於定義日志輸出的處理業務
  3 /// </summary>
  4 public abstract class LogBase : ILog
  5 {
  6     /// <summary>
  7     /// 獲取日志輸出處理委托實例
  8     /// </summary>
  9     /// <param name="level">日志輸出級別</param>
 10     /// <param name="message">日志消息</param>
 11     /// <param name="exception">日志異常</param>
 12     protected abstract void Write(LogLevel level, object message, Exception exception);
 13 
 14     #region Implementation of ILog
 15 
 16     /// <summary>
 17     /// 獲取 是否允許輸出<see cref="LogLevel.Trace"/>級別的日志
 18     /// </summary>
 19     public abstract bool IsTraceEnabled { get; }
 20 
 21     /// <summary>
 22     /// 獲取 是否允許輸出<see cref="LogLevel.Debug"/>級別的日志
 23     /// </summary>
 24     public abstract bool IsDebugEnabled { get; }
 25 
 26     /// <summary>
 27     /// 獲取 是否允許輸出<see cref="LogLevel.Info"/>級別的日志
 28     /// </summary>
 29     public abstract bool IsInfoEnabled { get; }
 30 
 31     /// <summary>
 32     /// 獲取 是否允許輸出<see cref="LogLevel.Warn"/>級別的日志
 33     /// </summary>
 34     public abstract bool IsWarnEnabled { get; }
 35 
 36     /// <summary>
 37     /// 獲取 是否允許輸出<see cref="LogLevel.Error"/>級別的日志
 38     /// </summary>
 39     public abstract bool IsErrorEnabled { get; }
 40 
 41     /// <summary>
 42     /// 獲取 是否允許輸出<see cref="LogLevel.Fatal"/>級別的日志
 43     /// </summary>
 44     public abstract bool IsFatalEnabled { get; }
 45 
 46     /// <summary>
 47     /// 寫入<see cref="LogLevel.Trace"/>日志消息
 48     /// </summary>
 49     /// <param name="message">日志消息</param>
 50     public virtual void Trace<T>(T message)
 51     {
 52         if (IsTraceEnabled)
 53         {
 54             Write(LogLevel.Trace, message, null);
 55         }
 56     }
 57 
 58     /// <summary>
 59     /// 寫入<see cref="LogLevel.Trace"/>格式化日志消息
 60     /// </summary>
 61     /// <param name="format">日志消息格式</param>
 62     /// <param name="args">格式化參數</param>
 63     public virtual void Trace(string format, params object[] args)
 64     {
 65         if (IsTraceEnabled)
 66         {
 67             Write(LogLevel.Trace, string.Format(format, args), null);
 68         }
 69     }
 70 
 71     /// <summary>
 72     /// 寫入<see cref="LogLevel.Debug"/>日志消息
 73     /// </summary>
 74     /// <param name="message">日志消息</param>
 75     public virtual void Debug<T>(T message)
 76     {
 77         if (IsDebugEnabled)
 78         {
 79             Write(LogLevel.Debug, message, null);
 80         }
 81     }
 82 
 83     /// <summary>
 84     /// 寫入<see cref="LogLevel.Debug"/>格式化日志消息
 85     /// </summary>
 86     /// <param name="format">日志消息格式</param>
 87     /// <param name="args">格式化參數</param>
 88     public virtual void Debug(string format, params object[] args)
 89     {
 90         if (IsDebugEnabled)
 91         {
 92             Write(LogLevel.Debug, string.Format(format, args), null);
 93         }
 94     }
 95 
 96     /// <summary>
 97     /// 寫入<see cref="LogLevel.Info"/>日志消息
 98     /// </summary>
 99     /// <param name="message">日志消息</param>
100     public virtual void Info<T>(T message)
101     {
102         if (IsInfoEnabled)
103         {
104             Write(LogLevel.Info, message, null);
105         }
106     }
107 
108     /// <summary>
109     /// 寫入<see cref="LogLevel.Info"/>格式化日志消息
110     /// </summary>
111     /// <param name="format">日志消息格式</param>
112     /// <param name="args">格式化參數</param>
113     public virtual void Info(string format, params object[] args)
114     {
115         if (IsInfoEnabled)
116         {
117             Write(LogLevel.Info, string.Format(format, args), null);
118         }
119     }
120 
121     /// <summary>
122     /// 寫入<see cref="LogLevel.Warn"/>日志消息
123     /// </summary>
124     /// <param name="message">日志消息</param>
125     public virtual void Warn<T>(T message)
126     {
127         if (IsWarnEnabled)
128         {
129             Write(LogLevel.Warn, message, null);
130         }
131     }
132 
133     /// <summary>
134     /// 寫入<see cref="LogLevel.Warn"/>格式化日志消息
135     /// </summary>
136     /// <param name="format">日志消息格式</param>
137     /// <param name="args">格式化參數</param>
138     public virtual void Warn(string format, params object[] args)
139     {
140         if (IsWarnEnabled)
141         {
142             Write(LogLevel.Warn, string.Format(format, args), null);
143         }
144     }
145 
146     /// <summary>
147     /// 寫入<see cref="LogLevel.Error"/>日志消息
148     /// </summary>
149     /// <param name="message">日志消息</param>
150     public virtual void Error<T>(T message)
151     {
152         if (IsErrorEnabled)
153         {
154             Write(LogLevel.Error, message, null);
155         }
156     }
157 
158     /// <summary>
159     /// 寫入<see cref="LogLevel.Error"/>格式化日志消息
160     /// </summary>
161     /// <param name="format">日志消息格式</param>
162     /// <param name="args">格式化參數</param>
163     public void Error(string format, params object[] args)
164     {
165         if (IsErrorEnabled)
166         {
167             Write(LogLevel.Error, string.Format(format, args), null);
168         }
169     }
170 
171     /// <summary>
172     /// 寫入<see cref="LogLevel.Error"/>日志消息,並記錄異常
173     /// </summary>
174     /// <param name="message">日志消息</param>
175     /// <param name="exception">異常</param>
176     public virtual void Error<T>(T message, Exception exception)
177     {
178         if (IsErrorEnabled)
179         {
180             Write(LogLevel.Error, message, exception);
181         }
182     }
183 
184     /// <summary>
185     /// 寫入<see cref="LogLevel.Error"/>格式化日志消息,並記錄異常
186     /// </summary>
187     /// <param name="format">日志消息格式</param>
188     /// <param name="exception">異常</param>
189     /// <param name="args">格式化參數</param>
190     public virtual void Error(string format, Exception exception, params object[] args)
191     {
192         if (IsErrorEnabled)
193         {
194             Write(LogLevel.Error, string.Format(format, args), exception);
195         }
196     }
197 
198     /// <summary>
199     /// 寫入<see cref="LogLevel.Fatal"/>日志消息
200     /// </summary>
201     /// <param name="message">日志消息</param>
202     public virtual void Fatal<T>(T message)
203     {
204         if (IsFatalEnabled)
205         {
206             Write(LogLevel.Fatal, message, null);
207         }
208     }
209 
210     /// <summary>
211     /// 寫入<see cref="LogLevel.Fatal"/>格式化日志消息
212     /// </summary>
213     /// <param name="format">日志消息格式</param>
214     /// <param name="args">格式化參數</param>
215     public void Fatal(string format, params object[] args)
216     {
217         if (IsFatalEnabled)
218         {
219             Write(LogLevel.Fatal, string.Format(format, args), null);
220         }
221     }
222 
223     /// <summary>
224     /// 寫入<see cref="LogLevel.Fatal"/>日志消息,並記錄異常
225     /// </summary>
226     /// <param name="message">日志消息</param>
227     /// <param name="exception">異常</param>
228     public virtual void Fatal<T>(T message, Exception exception)
229     {
230         if (IsFatalEnabled)
231         {
232             Write(LogLevel.Fatal, message, exception);
233         }
234     }
235 
236     /// <summary>
237     /// 寫入<see cref="LogLevel.Fatal"/>格式化日志消息,並記錄異常
238     /// </summary>
239     /// <param name="format">日志消息格式</param>
240     /// <param name="exception">異常</param>
241     /// <param name="args">格式化參數</param>
242     public virtual void Fatal(string format, Exception exception, params object[] args)
243     {
244         if (IsFatalEnabled)
245         {
246             Write(LogLevel.Fatal, string.Format(format, args), exception);
247         }
248     }
249 
250     #endregion
251 }

   需要將日志輸出應用到具體的技術實現時,只要以相應的技術實現 CachingLoggerAdapterBase 與 LogBase 兩個基類即可。

日志記錄處理

   有了上面定義的 ILoggerAdapter 與 ILog 以及 LogManager 對日志輸出適配器的管理,實現日志記錄已經是呼之欲出了。日志記錄的執行,主要通過下面這個內部類來完成。

  1 /// <summary>
  2 /// 日志記錄者,日志記錄輸入端
  3 /// </summary>
  4 internal sealed class Logger : ILogger
  5 {
  6     internal Logger(Type type)
  7         : this(type.FullName)
  8     { }
  9 
 10     internal Logger(string name)
 11     {
 12         Name = name;
 13         EntryLevel = ConfigurationManager.AppSettings.Get("OSharp-EntryLogLevel").CastTo(LogLevel.Off);
 14     }
 15 
 16     /// <summary>
 17     /// 獲取 日志記錄者名稱
 18     /// </summary>
 19     public string Name { get; private set; }
 20 
 21     /// <summary>
 22     /// 獲取或設置 日志級別的入口控制,級別決定是否執行相應級別的日志記錄功能
 23     /// </summary>
 24     public LogLevel EntryLevel { get; set; }
 25 
 26     #region Implementation of ILogger
 27 
 28     /// <summary>
 29     /// 寫入<see cref="LogLevel.Trace"/>日志消息
 30     /// </summary>
 31     /// <param name="message">日志消息</param>
 32     public void Trace<T>(T message)
 33     {
 34         if (!IsEnabledFor(LogLevel.Trace))
 35         {
 36             return;
 37         }
 38         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
 39         {
 40             log.Trace(message);
 41         }
 42     }
 43 
 44     /// <summary>
 45     /// 寫入<see cref="LogLevel.Trace"/>格式化日志消息
 46     /// </summary>
 47     /// <param name="format">日志消息格式</param>
 48     /// <param name="args">格式化參數</param>
 49     public void Trace(string format, params object[] args)
 50     {
 51         if (!IsEnabledFor(LogLevel.Trace))
 52         {
 53             return;
 54         }
 55         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
 56         {
 57             log.Trace(format, args);
 58         }
 59     }
 60 
 61     /// <summary>
 62     /// 寫入<see cref="LogLevel.Debug"/>日志消息
 63     /// </summary>
 64     /// <param name="message">日志消息</param>
 65     public void Debug<T>(T message)
 66     {
 67         if (!IsEnabledFor(LogLevel.Debug))
 68         {
 69             return;
 70         }
 71         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
 72         {
 73             log.Debug(message);
 74         }
 75     }
 76 
 77     /// <summary>
 78     /// 寫入<see cref="LogLevel.Debug"/>格式化日志消息
 79     /// </summary>
 80     /// <param name="format">日志消息格式</param>
 81     /// <param name="args">格式化參數</param>
 82     public void Debug(string format, params object[] args)
 83     {
 84         if (!IsEnabledFor(LogLevel.Debug))
 85         {
 86             return;
 87         }
 88         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
 89         {
 90             log.Debug(format, args);
 91         }
 92     }
 93 
 94     /// <summary>
 95     /// 寫入<see cref="LogLevel.Info"/>日志消息
 96     /// </summary>
 97     /// <param name="message">日志消息</param>
 98     public void Info<T>(T message)
 99     {
100         if (!IsEnabledFor(LogLevel.Info))
101         {
102             return;
103         }
104         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
105         {
106             log.Info(message);
107         }
108     }
109 
110     /// <summary>
111     /// 寫入<see cref="LogLevel.Info"/>格式化日志消息
112     /// </summary>
113     /// <param name="format">日志消息格式</param>
114     /// <param name="args">格式化參數</param>
115     public void Info(string format, params object[] args)
116     {
117         if (!IsEnabledFor(LogLevel.Info))
118         {
119             return;
120         }
121         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
122         {
123             log.Info(format, args);
124         }
125     }
126 
127     /// <summary>
128     /// 寫入<see cref="LogLevel.Warn"/>日志消息
129     /// </summary>
130     /// <param name="message">日志消息</param>
131     public void Warn<T>(T message)
132     {
133         if (!IsEnabledFor(LogLevel.Warn))
134         {
135             return;
136         }
137         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
138         {
139             log.Warn(message);
140         }
141     }
142 
143     /// <summary>
144     /// 寫入<see cref="LogLevel.Warn"/>格式化日志消息
145     /// </summary>
146     /// <param name="format">日志消息格式</param>
147     /// <param name="args">格式化參數</param>
148     public void Warn(string format, params object[] args)
149     {
150         if (!IsEnabledFor(LogLevel.Warn))
151         {
152             return;
153         }
154         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
155         {
156             log.Warn(format, args);
157         }
158     }
159 
160     /// <summary>
161     /// 寫入<see cref="LogLevel.Error"/>日志消息
162     /// </summary>
163     /// <param name="message">日志消息</param>
164     public void Error<T>(T message)
165     {
166         if (!IsEnabledFor(LogLevel.Error))
167         {
168             return;
169         }
170         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
171         {
172             log.Error(message);
173         }
174     }
175 
176     /// <summary>
177     /// 寫入<see cref="LogLevel.Error"/>格式化日志消息
178     /// </summary>
179     /// <param name="format">日志消息格式</param>
180     /// <param name="args">格式化參數</param>
181     public void Error(string format, params object[] args)
182     {
183         if (!IsEnabledFor(LogLevel.Error))
184         {
185             return;
186         }
187         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
188         {
189             log.Error(format, args);
190         }
191     }
192 
193     /// <summary>
194     /// 寫入<see cref="LogLevel.Error"/>日志消息,並記錄異常
195     /// </summary>
196     /// <param name="message">日志消息</param>
197     /// <param name="exception">異常</param>
198     public void Error<T>(T message, Exception exception)
199     {
200         if (!IsEnabledFor(LogLevel.Error))
201         {
202             return;
203         }
204         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
205         {
206             log.Error(message, exception);
207         }
208     }
209 
210     /// <summary>
211     /// 寫入<see cref="LogLevel.Error"/>格式化日志消息,並記錄異常
212     /// </summary>
213     /// <param name="format">日志消息格式</param>
214     /// <param name="exception">異常</param>
215     /// <param name="args">格式化參數</param>
216     public void Error(string format, Exception exception, params object[] args)
217     {
218         if (!IsEnabledFor(LogLevel.Error))
219         {
220             return;
221         }
222         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
223         {
224             log.Error(format, exception, args);
225         }
226     }
227 
228     /// <summary>
229     /// 寫入<see cref="LogLevel.Fatal"/>日志消息
230     /// </summary>
231     /// <param name="message">日志消息</param>
232     public void Fatal<T>(T message)
233     {
234         if (!IsEnabledFor(LogLevel.Fatal))
235         {
236             return;
237         }
238         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
239         {
240             log.Fatal(message);
241         }
242     }
243 
244     /// <summary>
245     /// 寫入<see cref="LogLevel.Fatal"/>格式化日志消息
246     /// </summary>
247     /// <param name="format">日志消息格式</param>
248     /// <param name="args">格式化參數</param>
249     public void Fatal(string format, params object[] args)
250     {
251         if (!IsEnabledFor(LogLevel.Fatal))
252         {
253             return;
254         }
255         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
256         {
257             log.Fatal(format, args);
258         }
259     }
260 
261     /// <summary>
262     /// 寫入<see cref="LogLevel.Fatal"/>日志消息,並記錄異常
263     /// </summary>
264     /// <param name="message">日志消息</param>
265     /// <param name="exception">異常</param>
266     public void Fatal<T>(T message, Exception exception)
267     {
268         if (!IsEnabledFor(LogLevel.Fatal))
269         {
270             return;
271         }
272         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
273         {
274             log.Fatal(message, exception);
275         }
276     }
277 
278     /// <summary>
279     /// 寫入<see cref="LogLevel.Fatal"/>格式化日志消息,並記錄異常
280     /// </summary>
281     /// <param name="format">日志消息格式</param>
282     /// <param name="exception">異常</param>
283     /// <param name="args">格式化參數</param>
284     public void Fatal(string format, Exception exception, params object[] args)
285     {
286         if (!IsEnabledFor(LogLevel.Fatal))
287         {
288             return;
289         }
290         foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
291         {
292             log.Fatal(format, exception, args);
293         }
294     }
295 
296     #endregion
297 
298     #region 私有方法
299 
300     private bool IsEnabledFor(LogLevel level)
301     {
302         return level >= EntryLevel;
303     }
304 
305     #endregion
306 }

 

  這個日志記錄類 Logger ,使用引用類(需要進行日志記錄的類)的名稱來獲取具體的實例,並緩存到 LogManager 中。

  在執行日志記錄的時候, Logger 類將按如下邏輯進行處理:

  1. 先檢查日志輸入級別許可( EntryLevel 屬性,通過鍵名“OSharp-EntryLogLevel”的AppSettings來定義,默認為 LogLevel.Off ),如果記錄級別小於允許級別,則攔截。
  2. 從 LogManager 的日志輸出適配器倉庫中篩選出所有名稱 Name 與當前 Logger 對象名稱相同的適配器
  3. 使用篩選出來的適配器獲取具體的 日志輸出者適配對象(ILog)
  4. 使用獲取到的適配對象(ILog)中的具體日志記錄實現(重寫的Write方法)進行日志內容的輸出

日志系統的應用(以log4net為例)

  下面來以log4net為實例,看看怎樣來使用 OSharp 中定義的日志系統。

日志輸出者適配類

  日志輸出者適配類,主要是根據 log4net 的現有配置定義OSharp中的日志輸出級別,並使用log4net實現日志輸出的 Write 操作。

 1 /// <summary>
 2 /// log4net 日志輸出者適配類
 3 /// </summary>
 4 internal class Log4NetLog : LogBase
 5 {
 6     private static readonly Type DeclaringType = typeof(Log4NetLog);
 7     private readonly ILogger _logger;
 8 
 9     /// <summary>
10     /// 初始化一個<see cref="Log4NetLog"/>類型的新實例
11     /// </summary>
12     public Log4NetLog(ILoggerWrapper wrapper)
13     {
14         _logger = wrapper.Logger;
15     }
16 
17     #region Overrides of LogBase
18 
19     /// <summary>
20     /// 獲取日志輸出處理委托實例
21     /// </summary>
22     /// <param name="level">日志輸出級別</param>
23     /// <param name="message">日志消息</param>
24     /// <param name="exception">日志異常</param>
25     protected override void Write(LogLevel level, object message, Exception exception)
26     {
27         Level log4NetLevel = GetLevel(level);
28         _logger.Log(DeclaringType, log4NetLevel, message, exception);
29     }
30 
31     /// <summary>
32     /// 獲取 是否允許輸出<see cref="LogLevel.Trace"/>級別的日志
33     /// </summary>
34     public override bool IsTraceEnabled { get { return _logger.IsEnabledFor(Level.Trace); } }
35 
36     /// <summary>
37     /// 獲取 是否允許輸出<see cref="LogLevel.Debug"/>級別的日志
38     /// </summary>
39     public override bool IsDebugEnabled { get { return _logger.IsEnabledFor(Level.Debug); } }
40 
41     /// <summary>
42     /// 獲取 是否允許輸出<see cref="LogLevel.Info"/>級別的日志
43     /// </summary>
44     public override bool IsInfoEnabled { get { return _logger.IsEnabledFor(Level.Info); } }
45 
46     /// <summary>
47     /// 獲取 是否允許輸出<see cref="LogLevel.Warn"/>級別的日志
48     /// </summary>
49     public override bool IsWarnEnabled { get { return _logger.IsEnabledFor(Level.Warn); } }
50 
51     /// <summary>
52     /// 獲取 是否允許輸出<see cref="LogLevel.Error"/>級別的日志
53     /// </summary>
54     public override bool IsErrorEnabled { get { return _logger.IsEnabledFor(Level.Error); } }
55 
56     /// <summary>
57     /// 獲取 是否允許輸出<see cref="LogLevel.Fatal"/>級別的日志
58     /// </summary>
59     public override bool IsFatalEnabled { get { return _logger.IsEnabledFor(Level.Fatal); } }
60 
61     #endregion
62     
63     private static Level GetLevel(LogLevel level)
64     {
65         switch (level)
66         {
67             case LogLevel.All:
68                 return Level.All;
69             case LogLevel.Trace:
70                 return Level.Trace;
71             case LogLevel.Debug:
72                 return Level.Debug;
73             case LogLevel.Info:
74                 return Level.Info;
75             case LogLevel.Warn:
76                 return Level.Warn;
77             case LogLevel.Error:
78                 return Level.Error;
79             case LogLevel.Fatal:
80                 return Level.Fatal;
81             case LogLevel.Off:
82                 return Level.Off;
83             default:
84                 return Level.Off;
85         }
86     }
87 }

日志輸出適配器

   日志輸出適配器,主要是實現怎樣創建 log4net 日志對象,並把日志對象轉換為上面定義的 日志輸出者適配對象:

 1 /// <summary>
 2 /// log4net 日志輸出適配器
 3 /// </summary>
 4 public class Log4NetLoggerAdapter : LoggerAdapterBase
 5 {
 6     /// <summary>
 7     /// 初始化一個<see cref="Log4NetLoggerAdapter"/>類型的新實例
 8     /// </summary>
 9     public Log4NetLoggerAdapter()
10     {
11         RollingFileAppender appender = new RollingFileAppender
12         {
13             Name = "root",
14             File = "logs\\log_",
15             AppendToFile = true,
16             LockingModel = new FileAppender.MinimalLock(),
17             RollingStyle = RollingFileAppender.RollingMode.Date,
18             DatePattern = "yyyyMMdd-HH\".log\"",
19             StaticLogFileName = false,
20             Threshold = Level.Debug,
21             MaxSizeRollBackups = 10,
22             Layout = new PatternLayout("%n[%d{yyyy-MM-dd HH:mm:ss.fff}] %-5p %c %t %w %n%m%n")
23         };
24         appender.ClearFilters();
25         appender.AddFilter(new LevelMatchFilter { LevelToMatch = Level.Info });
26         BasicConfigurator.Configure(appender);
27         appender.ActivateOptions();
28     }
29 
30 
31     #region Overrides of LoggerAdapterBase
32 
33     /// <summary>
34     /// 創建指定名稱的緩存實例
35     /// </summary>
36     /// <param name="name">指定名稱</param>
37     /// <returns></returns>
38     protected override ILog CreateLogger(string name)
39     {
40         log4net.ILog log = log4net.LogManager.GetLogger(name);
41         return new Log4NetLog(log);
42     }
43 
44     #endregion
45 }

日志環境初始化

   日志系統的運行環境初始化工作在Global中進行,要做的事很簡單,只是把日志輸出適配器 Log4NetLoggerAdapter 的實例加入到 LogManager 中的 日志輸出適配器倉庫中。

1  private static void LoggingInitialize()
2  {
3      Log4NetLoggerAdapter adapter = new Log4NetLoggerAdapter();
4      LogManager.AddLoggerAdapter(adapter);
5  }

日志輸出

  進行初始化之后,就可以在業務代碼中使用日志記錄功能了。以類名從 LogManager 獲取 日志記錄對象 的 ILogger 實例,再調用相應的日志記錄方法來記錄日志信息。

  在網站首頁獲取日志記錄實例,輸出日志信息:

  

  在網站后台管理首頁獲取日志記錄實例,輸出日志信息:

  

  運行項目,得到我們預想中的日志輸出結果:

  

開源說明

github.com

   OSharp項目已在github.com上開源,地址為:https://github.com/i66soft/osharp,歡迎閱讀代碼,歡迎 Fork,如果您認同 OSharp 項目的思想,歡迎參與 OSharp 項目的開發。

  在Visual Studio 2013中,可直接獲取 OSharp 的最新源代碼,獲取方式如下,地址為:https://github.com/i66soft/osharp.git

  

nuget

  OSharp的相關類庫已經發布到nuget上,歡迎試用,直接在nuget上搜索 “osharp” 關鍵字即可找到
  

列導航

本文已同步到系列目錄:OSharp快速開發框架解說系列


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM