• 【开源】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快速开发框架解说系列

  • 相关阅读:
    谈谈一些有趣的CSS题目(十二)-- 你该知道的字体 font-family
    谈谈一些有趣的CSS题目(十一)-- reset.css 知多少?
    【Web动画】SVG 实现复杂线条动画
    【Web动画】SVG 线条动画入门
    引人瞩目的 CSS 变量(CSS Variable)
    谈谈一些有趣的CSS题目(十)-- 结构性伪类选择器
    ROW_NUMBER() OVER函数的基本用法
    PL SQL笔记(三)
    pushState、replaceState、onpopstate 实现Ajax页面的前进后退刷新
    无聊的人用JS实现了一个简单的打地鼠游戏
  • 原文地址:https://www.cnblogs.com/guomingfeng/p/osharp-logging-design.html
Copyright © 2020-2023  润新知