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:关闭所有日志级别,不输出日志
 /// <summary>
/// 表示日志输出级别的枚举
/// </summary>
public enum LogLevel
{
/// <summary>
/// 输出所有级别的日志
/// </summary>
All = , /// <summary>
/// 表示跟踪的日志级别
/// </summary>
Trace = , /// <summary>
/// 表示调试的日志级别
/// </summary>
Debug = , /// <summary>
/// 表示消息的日志级别
/// </summary>
Info = , /// <summary>
/// 表示警告的日志级别
/// </summary>
Warn = , /// <summary>
/// 表示错误的日志级别
/// </summary>
Error = , /// <summary>
/// 表示严重错误的日志级别
/// </summary>
Fatal = , /// <summary>
/// 关闭所有日志,不输出日志
/// </summary>
Off =
}

  对于各个日志级别,日志系统都有相应的日志记录行为规范。各个级别都支持 泛型、格式化字符串 的输出,对于错误与严重错误的级别,支持 Exception 异常信息的支持。

  日志记录行为定义为 ILogger 接口:

 /// <summary>
/// 定义日志记录行为
/// </summary>
public interface ILogger
{
#region 方法 /// <summary>
/// 写入<see cref="LogLevel.Trace"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
void Trace<T>(T message); /// <summary>
/// 写入<see cref="LogLevel.Trace"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
void Trace(string format, params object[] args); /// <summary>
/// 写入<see cref="LogLevel.Debug"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
void Debug<T>(T message); /// <summary>
/// 写入<see cref="LogLevel.Debug"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
void Debug(string format, params object[] args); /// <summary>
/// 写入<see cref="LogLevel.Info"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
void Info<T>(T message); /// <summary>
/// 写入<see cref="LogLevel.Info"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
void Info(string format, params object[] args); /// <summary>
/// 写入<see cref="LogLevel.Warn"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
void Warn<T>(T message); /// <summary>
/// 写入<see cref="LogLevel.Warn"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
void Warn(string format, params object[] args); /// <summary>
/// 写入<see cref="LogLevel.Error"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
void Error<T>(T message); /// <summary>
/// 写入<see cref="LogLevel.Error"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
void Error(string format, params object[] args); /// <summary>
/// 写入<see cref="LogLevel.Error"/>日志消息,并记录异常
/// </summary>
/// <param name="message">日志消息</param>
/// <param name="exception">异常</param>
void Error<T>(T message, Exception exception); /// <summary>
/// 写入<see cref="LogLevel.Error"/>格式化日志消息,并记录异常
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="exception">异常</param>
/// <param name="args">格式化参数</param>
void Error(string format, Exception exception, params object[] args); /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
void Fatal<T>(T message); /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
void Fatal(string format, params object[] args); /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>日志消息,并记录异常
/// </summary>
/// <param name="message">日志消息</param>
/// <param name="exception">异常</param>
void Fatal<T>(T message, Exception exception); /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>格式化日志消息,并记录异常
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="exception">异常</param>
/// <param name="args">格式化参数</param>
void Fatal(string format, Exception exception, params object[] args); #endregion
}

日志输出适配设计

日志输出适配器

  不同的需求有不同的日志输出需求,通常我们会遇到如下的场景:

  1. 操作类日志,常常与业务相关,需要输出到数据库中,便于查看与追溯
  2. 实时监控系统中,日志需要实时输出到控制台,便于即时的反馈给观察者
  3. 对于错误异常类的信息,基查看人员通常是开发人员与运维人员,与业务相关度不大,用文本文件按天记录即可
  4. ...

  面对众多的需求场景,日志信息的输出形式就需灵活多变,必须能灵活替换或添加一个或多个输出行为。为应对这些需求,并充分把现有的输出行为利用上,OSharp 的日志系统的日志输出行为是灵活的,主要通过定义一系列的日志输出适配器来完成。

  日志输出适配器 ILoggerAdapter 定义如下,主要是定义获取 日志输出者适配对象 ILog 的创建行为:

 /// <summary>
/// 由指定类型获取<see cref="ILog"/>日志实例
/// </summary>
/// <param name="type">指定类型</param>
/// <returns></returns>
ILog GetLogger(Type type); /// <summary>
/// 由指定名称获取<see cref="ILog"/>日志实例
/// </summary>
/// <param name="name">指定名称</param>
/// <returns></returns>
ILog GetLogger(string name);

  每一个日志输出适配器,都对应着一系列具体的日志输出实现技术的日志输出适配对象(如Log4Net中的 log4net.Core.ILogger 类的对象),适配器需要自行管理这些对象的创建与销毁行为,并把创建的日志输出适配对象按名称缓存起来。OSharp中通过定义一个通用适配器基类 LoggerAdapterBase 来规范这些行为。

 /// <summary>
/// 按名称缓存的日志输出适配器基类,用于创建并管理指定类型的日志输出者实例
/// </summary>
public abstract class LoggerAdapterBase : ILoggerAdapter
{
private readonly ConcurrentDictionary<string, ILog> _cacheLoggers; /// <summary>
/// 初始化一个<see cref="LoggerAdapterBase"/>类型的新实例
/// </summary>
protected LoggerAdapterBase()
{
_cacheLoggers = new ConcurrentDictionary<string, ILog>();
} #region Implementation of ILoggerFactoryAdapter /// <summary>
/// 由指定类型获取<see cref="ILog"/>日志实例
/// </summary>
/// <param name="type">指定类型</param>
/// <returns></returns>
public ILog GetLogger(Type type)
{
type.CheckNotNull("type");
return GetLoggerInternal(type.FullName);
} /// <summary>
/// 由指定名称获取<see cref="ILog"/>日志实例
/// </summary>
/// <param name="name">指定名称</param>
/// <returns></returns>
public ILog GetLogger(string name)
{
name.CheckNotNullOrEmpty("name");
return GetLoggerInternal(name);
} #endregion /// <summary>
/// 创建指定名称的缓存实例
/// </summary>
/// <param name="name">指定名称</param>
/// <returns></returns>
protected abstract ILog CreateLogger(string name); /// <summary>
/// 清除缓存中的日志实例
/// </summary>
protected virtual void ClearLoggerCache()
{
_cacheLoggers.Clear();
} private ILog GetLoggerInternal(string name)
{
ILog log;
if (_cacheLoggers.TryGetValue(name, out log))
{
return log;
}
log = CreateLogger(name);
if (log == null)
{
throw new NotSupportedException(Resources.Logging_CreateLogInstanceReturnNull.FormatWith(name, GetType().FullName));
}
_cacheLoggers[name] = log;
return log;
}
}

  适配器基类中,定义了一个 ConcurrentDictionary<string, ILog> 仓库通过名称来管理适配之后的 ILog 对象,并向派生类开放了一个 protected abstract ILog CreateLogger(string name); 来获取 ILog 对象的具体适配实现。

日志输出者适配对象

  日志输出者适配对象 ILog 接口派生自 ILogger ,添加了日志输出的许可属性,用于定义具体的日志输出者对象:

 /// <summary>
/// 表示日志实例的接口
/// </summary>
public interface ILog : ILogger
{
#region 属性 /// <summary>
/// 获取 是否允许<see cref="LogLevel.Trace"/>级别的日志
/// </summary>
bool IsTraceEnabled { get; } /// <summary>
/// 获取 是否允许<see cref="LogLevel.Debug"/>级别的日志
/// </summary>
bool IsDebugEnabled { get; } /// <summary>
/// 获取 是否允许<see cref="LogLevel.Info"/>级别的日志
/// </summary>
bool IsInfoEnabled { get; } /// <summary>
/// 获取 是否允许<see cref="LogLevel.Warn"/>级别的日志
/// </summary>
bool IsWarnEnabled { get; } /// <summary>
/// 获取 是否允许<see cref="LogLevel.Error"/>级别的日志
/// </summary>
bool IsErrorEnabled { get; } /// <summary>
/// 获取 是否允许<see cref="LogLevel.Fatal"/>级别的日志
/// </summary>
bool IsFatalEnabled { get; } #endregion }

  日志输出者适配类,主要是使用现有的日志输出实现类(如 log4net.Core.ILogger )去实现具体的日志输出行为,在下面这个 日志输出者适配基类 LogBase 中,主要是通过重写 Write 方法来实现。

 /// <summary>
/// 日志输出者适配基类,用于定义日志输出的处理业务
/// </summary>
public abstract class LogBase : ILog
{
/// <summary>
/// 获取日志输出处理委托实例
/// </summary>
/// <param name="level">日志输出级别</param>
/// <param name="message">日志消息</param>
/// <param name="exception">日志异常</param>
protected abstract void Write(LogLevel level, object message, Exception exception); #region Implementation of ILog /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Trace"/>级别的日志
/// </summary>
public abstract bool IsTraceEnabled { get; } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Debug"/>级别的日志
/// </summary>
public abstract bool IsDebugEnabled { get; } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Info"/>级别的日志
/// </summary>
public abstract bool IsInfoEnabled { get; } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Warn"/>级别的日志
/// </summary>
public abstract bool IsWarnEnabled { get; } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Error"/>级别的日志
/// </summary>
public abstract bool IsErrorEnabled { get; } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Fatal"/>级别的日志
/// </summary>
public abstract bool IsFatalEnabled { get; } /// <summary>
/// 写入<see cref="LogLevel.Trace"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public virtual void Trace<T>(T message)
{
if (IsTraceEnabled)
{
Write(LogLevel.Trace, message, null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Trace"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public virtual void Trace(string format, params object[] args)
{
if (IsTraceEnabled)
{
Write(LogLevel.Trace, string.Format(format, args), null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Debug"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public virtual void Debug<T>(T message)
{
if (IsDebugEnabled)
{
Write(LogLevel.Debug, message, null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Debug"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public virtual void Debug(string format, params object[] args)
{
if (IsDebugEnabled)
{
Write(LogLevel.Debug, string.Format(format, args), null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Info"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public virtual void Info<T>(T message)
{
if (IsInfoEnabled)
{
Write(LogLevel.Info, message, null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Info"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public virtual void Info(string format, params object[] args)
{
if (IsInfoEnabled)
{
Write(LogLevel.Info, string.Format(format, args), null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Warn"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public virtual void Warn<T>(T message)
{
if (IsWarnEnabled)
{
Write(LogLevel.Warn, message, null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Warn"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public virtual void Warn(string format, params object[] args)
{
if (IsWarnEnabled)
{
Write(LogLevel.Warn, string.Format(format, args), null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public virtual void Error<T>(T message)
{
if (IsErrorEnabled)
{
Write(LogLevel.Error, message, null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Error(string format, params object[] args)
{
if (IsErrorEnabled)
{
Write(LogLevel.Error, string.Format(format, args), null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>日志消息,并记录异常
/// </summary>
/// <param name="message">日志消息</param>
/// <param name="exception">异常</param>
public virtual void Error<T>(T message, Exception exception)
{
if (IsErrorEnabled)
{
Write(LogLevel.Error, message, exception);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>格式化日志消息,并记录异常
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="exception">异常</param>
/// <param name="args">格式化参数</param>
public virtual void Error(string format, Exception exception, params object[] args)
{
if (IsErrorEnabled)
{
Write(LogLevel.Error, string.Format(format, args), exception);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public virtual void Fatal<T>(T message)
{
if (IsFatalEnabled)
{
Write(LogLevel.Fatal, message, null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Fatal(string format, params object[] args)
{
if (IsFatalEnabled)
{
Write(LogLevel.Fatal, string.Format(format, args), null);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>日志消息,并记录异常
/// </summary>
/// <param name="message">日志消息</param>
/// <param name="exception">异常</param>
public virtual void Fatal<T>(T message, Exception exception)
{
if (IsFatalEnabled)
{
Write(LogLevel.Fatal, message, exception);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>格式化日志消息,并记录异常
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="exception">异常</param>
/// <param name="args">格式化参数</param>
public virtual void Fatal(string format, Exception exception, params object[] args)
{
if (IsFatalEnabled)
{
Write(LogLevel.Fatal, string.Format(format, args), exception);
}
} #endregion
}

  需要将日志输出应用到具体的技术实现时,只要以相应的技术实现 CachingLoggerAdapterBase 与 LogBase 两个基类即可。

日志记录处理

  有了上面定义的 ILoggerAdapter 与 ILog 以及 LogManager 对日志输出适配器的管理,实现日志记录已经是呼之欲出了。日志记录的执行,主要通过下面这个内部类来完成。

 /// <summary>
/// 日志记录者,日志记录输入端
/// </summary>
internal sealed class Logger : ILogger
{
internal Logger(Type type)
: this(type.FullName)
{ } internal Logger(string name)
{
Name = name;
EntryLevel = ConfigurationManager.AppSettings.Get("OSharp-EntryLogLevel").CastTo(LogLevel.Off);
} /// <summary>
/// 获取 日志记录者名称
/// </summary>
public string Name { get; private set; } /// <summary>
/// 获取或设置 日志级别的入口控制,级别决定是否执行相应级别的日志记录功能
/// </summary>
public LogLevel EntryLevel { get; set; } #region Implementation of ILogger /// <summary>
/// 写入<see cref="LogLevel.Trace"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public void Trace<T>(T message)
{
if (!IsEnabledFor(LogLevel.Trace))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Trace(message);
}
} /// <summary>
/// 写入<see cref="LogLevel.Trace"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Trace(string format, params object[] args)
{
if (!IsEnabledFor(LogLevel.Trace))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Trace(format, args);
}
} /// <summary>
/// 写入<see cref="LogLevel.Debug"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public void Debug<T>(T message)
{
if (!IsEnabledFor(LogLevel.Debug))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Debug(message);
}
} /// <summary>
/// 写入<see cref="LogLevel.Debug"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Debug(string format, params object[] args)
{
if (!IsEnabledFor(LogLevel.Debug))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Debug(format, args);
}
} /// <summary>
/// 写入<see cref="LogLevel.Info"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public void Info<T>(T message)
{
if (!IsEnabledFor(LogLevel.Info))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Info(message);
}
} /// <summary>
/// 写入<see cref="LogLevel.Info"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Info(string format, params object[] args)
{
if (!IsEnabledFor(LogLevel.Info))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Info(format, args);
}
} /// <summary>
/// 写入<see cref="LogLevel.Warn"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public void Warn<T>(T message)
{
if (!IsEnabledFor(LogLevel.Warn))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Warn(message);
}
} /// <summary>
/// 写入<see cref="LogLevel.Warn"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Warn(string format, params object[] args)
{
if (!IsEnabledFor(LogLevel.Warn))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Warn(format, args);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public void Error<T>(T message)
{
if (!IsEnabledFor(LogLevel.Error))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Error(message);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Error(string format, params object[] args)
{
if (!IsEnabledFor(LogLevel.Error))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Error(format, args);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>日志消息,并记录异常
/// </summary>
/// <param name="message">日志消息</param>
/// <param name="exception">异常</param>
public void Error<T>(T message, Exception exception)
{
if (!IsEnabledFor(LogLevel.Error))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Error(message, exception);
}
} /// <summary>
/// 写入<see cref="LogLevel.Error"/>格式化日志消息,并记录异常
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="exception">异常</param>
/// <param name="args">格式化参数</param>
public void Error(string format, Exception exception, params object[] args)
{
if (!IsEnabledFor(LogLevel.Error))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Error(format, exception, args);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>日志消息
/// </summary>
/// <param name="message">日志消息</param>
public void Fatal<T>(T message)
{
if (!IsEnabledFor(LogLevel.Fatal))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Fatal(message);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>格式化日志消息
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="args">格式化参数</param>
public void Fatal(string format, params object[] args)
{
if (!IsEnabledFor(LogLevel.Fatal))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Fatal(format, args);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>日志消息,并记录异常
/// </summary>
/// <param name="message">日志消息</param>
/// <param name="exception">异常</param>
public void Fatal<T>(T message, Exception exception)
{
if (!IsEnabledFor(LogLevel.Fatal))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Fatal(message, exception);
}
} /// <summary>
/// 写入<see cref="LogLevel.Fatal"/>格式化日志消息,并记录异常
/// </summary>
/// <param name="format">日志消息格式</param>
/// <param name="exception">异常</param>
/// <param name="args">格式化参数</param>
public void Fatal(string format, Exception exception, params object[] args)
{
if (!IsEnabledFor(LogLevel.Fatal))
{
return;
}
foreach (ILog log in LogManager.Adapters.Select(adapter => adapter.GetLogger(Name)))
{
log.Fatal(format, exception, args);
}
} #endregion #region 私有方法 private bool IsEnabledFor(LogLevel level)
{
return level >= EntryLevel;
} #endregion
}

  这个日志记录类 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 操作。

 /// <summary>
/// log4net 日志输出者适配类
/// </summary>
internal class Log4NetLog : LogBase
{
private static readonly Type DeclaringType = typeof(Log4NetLog);
private readonly ILogger _logger; /// <summary>
/// 初始化一个<see cref="Log4NetLog"/>类型的新实例
/// </summary>
public Log4NetLog(ILoggerWrapper wrapper)
{
_logger = wrapper.Logger;
} #region Overrides of LogBase /// <summary>
/// 获取日志输出处理委托实例
/// </summary>
/// <param name="level">日志输出级别</param>
/// <param name="message">日志消息</param>
/// <param name="exception">日志异常</param>
protected override void Write(LogLevel level, object message, Exception exception)
{
Level log4NetLevel = GetLevel(level);
_logger.Log(DeclaringType, log4NetLevel, message, exception);
} /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Trace"/>级别的日志
/// </summary>
public override bool IsTraceEnabled { get { return _logger.IsEnabledFor(Level.Trace); } } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Debug"/>级别的日志
/// </summary>
public override bool IsDebugEnabled { get { return _logger.IsEnabledFor(Level.Debug); } } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Info"/>级别的日志
/// </summary>
public override bool IsInfoEnabled { get { return _logger.IsEnabledFor(Level.Info); } } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Warn"/>级别的日志
/// </summary>
public override bool IsWarnEnabled { get { return _logger.IsEnabledFor(Level.Warn); } } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Error"/>级别的日志
/// </summary>
public override bool IsErrorEnabled { get { return _logger.IsEnabledFor(Level.Error); } } /// <summary>
/// 获取 是否允许输出<see cref="LogLevel.Fatal"/>级别的日志
/// </summary>
public override bool IsFatalEnabled { get { return _logger.IsEnabledFor(Level.Fatal); } } #endregion private static Level GetLevel(LogLevel level)
{
switch (level)
{
case LogLevel.All:
return Level.All;
case LogLevel.Trace:
return Level.Trace;
case LogLevel.Debug:
return Level.Debug;
case LogLevel.Info:
return Level.Info;
case LogLevel.Warn:
return Level.Warn;
case LogLevel.Error:
return Level.Error;
case LogLevel.Fatal:
return Level.Fatal;
case LogLevel.Off:
return Level.Off;
default:
return Level.Off;
}
}
}

日志输出适配器

  日志输出适配器,主要是实现怎样创建 log4net 日志对象,并把日志对象转换为上面定义的 日志输出者适配对象:

 /// <summary>
/// log4net 日志输出适配器
/// </summary>
public class Log4NetLoggerAdapter : LoggerAdapterBase
{
/// <summary>
/// 初始化一个<see cref="Log4NetLoggerAdapter"/>类型的新实例
/// </summary>
public Log4NetLoggerAdapter()
{
RollingFileAppender appender = new RollingFileAppender
{
Name = "root",
File = "logs\\log_",
AppendToFile = true,
LockingModel = new FileAppender.MinimalLock(),
RollingStyle = RollingFileAppender.RollingMode.Date,
DatePattern = "yyyyMMdd-HH\".log\"",
StaticLogFileName = false,
Threshold = Level.Debug,
MaxSizeRollBackups = ,
Layout = new PatternLayout("%n[%d{yyyy-MM-dd HH:mm:ss.fff}] %-5p %c %t %w %n%m%n")
};
appender.ClearFilters();
appender.AddFilter(new LevelMatchFilter { LevelToMatch = Level.Info });
BasicConfigurator.Configure(appender);
appender.ActivateOptions();
} #region Overrides of LoggerAdapterBase /// <summary>
/// 创建指定名称的缓存实例
/// </summary>
/// <param name="name">指定名称</param>
/// <returns></returns>
protected override ILog CreateLogger(string name)
{
log4net.ILog log = log4net.LogManager.GetLogger(name);
return new Log4NetLog(log);
} #endregion
}

日志环境初始化

  日志系统的运行环境初始化工作在Global中进行,要做的事很简单,只是把日志输出适配器 Log4NetLoggerAdapter 的实例加入到 LogManager 中的 日志输出适配器仓库中。

  private static void LoggingInitialize()
{
Log4NetLoggerAdapter adapter = new Log4NetLoggerAdapter();
LogManager.AddLoggerAdapter(adapter);
}

日志输出

  进行初始化之后,就可以在业务代码中使用日志记录功能了。以类名从 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快速开发框架解说系列

【开源】OSharp框架解说系列(6.1):日志系统设计的更多相关文章

  1. 【开源】OSharp框架解说系列(1):总体设计及系列导航

    系列文章导航 [开源]OSharp框架解说系列(1):总体设计 [开源]OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构 [开源]OSharp框架解说系列(2.2):EasyU ...

  2. 【开源】OSharp框架解说系列(5.2):EntityFramework数据层实现

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  3. 【开源】OSharp框架解说系列(5.1):EntityFramework数据层设计

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  4. 【开源】OSharp框架解说系列(4):架构分层及IoC

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  5. 【开源】OSharp框架解说系列(3):扩展方法

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  6. 【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  7. 【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  8. 【开源】OSharp3.0框架解说系列(6.2):操作日志与数据日志

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  9. 【开源】OSharp3.0框架解说系列:新版本说明及新功能规划预览

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

随机推荐

  1. 2014 summer training总结篇

    还有一周暑期集训就要结束了,从7月份结束军训到现在一个多月的时间,收获也是有的只不过与之前预想的相比显得十分微薄. 无论是前两天的两场个人赛还是之前的组队赛自己始终是在ranklist的后半部分.一开 ...

  2. [IOS]使用了cocoapods 抱错Pods was rejected as an implicit dependency for ‘libPods.a’ because its architectures ......

    Pods was rejected as an implicit dependency for ‘libPods.a’ because its architectures ‘i386’ didn’t ...

  3. 喜马拉雅FM抓包之旅

    一.概述 最近学院组织安排大面积实习工作,今天刚刚发布了喜马拉雅FM实习生招聘的面试通知.通知要求:公司采用开放式题目的方式进行筛选,申请的同学须完成如下题目 写程序输出喜马拉雅FM上与"卓 ...

  4. java-JDBC从数据库中读取数据并进行日期民族男女的转换

    代码如下: package com.itnba.maya.mysql; import java.sql.*; import java.text.SimpleDateFormat; public cla ...

  5. 工作中那些提高你效率的神器(第一篇)_Everything

    引言 无论是工作还是科研,我们都希望工作既快又好,然而大多数时候却迷失在繁杂的重复劳动中,久久无法摆脱繁杂的事情. 你是不是曾有这样一种想法:如果我有哆啦A梦的口袋,只要拿出神奇道具就可解当下棘手的问 ...

  6. 串口计时工具Grabserial简介及修改(添加输入功能)

    Grabserial是Tim Bird用python写的一个抓取串口的工具,这个工具能够为收到的每一行信息添加上时间戳. 如果想对启动时间进行优化的话,使用这个工具就可以简单地从串口输出分析出耗时. ...

  7. Android分享一款漂亮的折叠书架菜单

    一个Android折叠书架菜单,效果极佳,给人的视觉感觉很好,便于使用. FoldingMenu

  8. Git 常用命令大全

    Git常用操作命令: 1) 远程仓库相关命令 检出仓库:$ git clone git://github.com/jquery/jquery.git 查看远程仓库:$ git remote -v 添加 ...

  9. winform设置文本框宽度 根据文字数量和字体返回宽度

    _LinkLabel.Width = TextRenderer.MeasureText(_LinkLabel.Text, _LinkLabel.Font).Width;

  10. Windows环境安装Linux系统及JDK部署

    前言 由于我的笔记本有点问题,所以这周系统包括所有硬盘全部重装了,原来的Linux虚拟机都没了,因此才有了这篇文章和各位朋友们分享. 由于Linux环境的优越性(开源.低成本.安全性好.网络功能强大) ...