【开源】OSharp框架解说系列(6.1):日志系统设计
日志记录对于一个系统而言,重要性不言而喻。日志记录功能在系统开发阶段,往往容易被忽略。因为开发阶段,程序可以调试,可以反复的运行以查找问题。但在系统进入正常的运行维护阶段,特别是在进行审计统计的时候,追踪问题的时候,在追溯责任的时候,在系统出错的时候等等场景中,日志记录才会显示出它不可替代的作用。记录的日志,平时看似累赘,只有在需要的时候,才会拍大腿后悔当初为什么不把日志记录得详细些。日志系统,是一个非常基础的系统,但由于需求的复杂性,各个场景需要的日志分类,来源,输出方式各有不同,日志系统又是一个相对复杂的系统。下面我们就来解说一下,OSharp开发框架的日志系统设计中,怎样来应对这些复杂性。
OSharp是什么?
OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现。与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现。依赖注入、ORM、对象映射、日志、缓存等等功能,都只定义了一套最基础最通用的抽象封装,提供了一套统一的API、约定与规则,并定义了部分执行流程,主要是让项目在一定的规范下进行开发。所有的功能实现端,都是通过现有的成熟的第三方组件来实现的,除了EntityFramework之外,所有的第三方实现都可以轻松的替换成另一种第三方实现,OSharp框架正是要起隔离作用,保证这种变更不会对业务代码造成影响,使用统一的API来进行业务实现,解除与第三方实现的耦合,保持业务代码的规范与稳定。
本文已同步到系列目录:OSharp快速开发框架解说系列
前言
日志记录对于一个系统而言,重要性不言而喻。日志记录功能在系统开发阶段,往往容易被忽略。因为开发阶段,程序可以调试,可以反复的运行以查找问题。但在系统进入正常的运行维护阶段,特别是在进行审计统计的时候,追踪问题的时候,在追溯责任的时候,在系统出错的时候等等场景中,日志记录才会显示出它不可替代的作用。记录的日志,平时看似累赘,只有在需要的时候,才会拍大腿后悔当初为什么不把日志记录得详细些。
日志系统,是一个非常基础的系统,但由于需求的复杂性,各个场景需要的日志分类,来源,输出方式各有不同,日志系统又是一个相对复杂的系统。下面我们就来解说一下,OSharp开发框架的日志系统设计中,怎样来应对这些复杂性。
系统架构设计图
OSharp 开发框架的日志部分定义了一套基础的通用的日志记录协议,来创建并管理日志记录对象,并处理日志的输入逻辑,没有日志的输出实现,日志的输出完全由第三方日志组件来完成。基本架构图如下图所示:
项目代码组成:
日志记录的过程按如下步骤进行:
- 日志管理器 LogManager 中定义了两个仓库:日志记录者仓库 ConcurrentDictionary<string, ILogger> 与 日志输出者适配器仓库 ICollection<ILoggerAdapter>
- 具体的日志输出方式实现相应的日志输出适配器 ILoggerAdapter,并添加到 LogManager 的日志输出适配器仓库中
- 日志记录点使用名称通过 LogManager 创建(提取)日志记录者 Logger,并存回到 LogManager 的仓库中等下次备用
- 日志输出者调用 LogManager 中的所有 ILoggerAdapter 适配器(例如 Log4NetLoggerAdapter)输出日志
- 日志适配器 ILoggerAdapter 调用具体的日志输出者 LogBase(例如 Log4NetLog)进行日志的输出
OSharp 的日志系统在设计上有如下特点:
- 整个OSharp 日志系统只负责收集日志信息,并不关心日志的输出行为
- 具体的日志输出行为由各个输出方案实现 日志输出适配器 来完成
- 一条日志信息,可以有一个或多个日志输出途径
核心设计
下面我们来一步一步解析上图的设计思路。
日志记录行为约定
在OSharp的设计中,为了满足各种应用场景对日志输出级别的控制,定义了 8 个日志输出的级别:
- All:输出所有级别的日志
- Trace:输出跟踪级别的日志
- Debug:输出调试级别的日志
- Info:输出消息级别的日志
- Warn:输出警告级别的日志
- Error:输出错误级别的日志
- Fatal:输出严重错误级别的日志
- 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 }
日志输出适配设计
日志输出适配器
不同的需求有不同的日志输出需求,通常我们会遇到如下的场景:
- 操作类日志,常常与业务相关,需要输出到数据库中,便于查看与追溯
- 实时监控系统中,日志需要实时输出到控制台,便于即时的反馈给观察者
- 对于错误异常类的信息,基查看人员通常是开发人员与运维人员,与业务相关度不大,用文本文件按天记录即可
- …
面对众多的需求场景,日志信息的输出形式就需灵活多变,必须能灵活替换或添加一个或多个输出行为。为应对这些需求,并充分把现有的输出行为利用上,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 类将按如下逻辑进行处理:
- 先检查日志输入级别许可( EntryLevel 属性,通过键名“OSharp-EntryLogLevel”的AppSettings来定义,默认为 LogLevel.Off ),如果记录级别小于允许级别,则拦截。
- 从 LogManager 的日志输出适配器仓库中筛选出所有名称 Name 与当前 Logger 对象名称相同的适配器
- 使用筛选出来的适配器获取具体的 日志输出者适配对象(ILog)
- 使用获取到的适配对象(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快速开发框架解说系列