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. javascript中三种典型情况下this的含义

    this本意:基于函数的执行环境绑定. 1)一般函数内部,返回的是window(作用域链中的第二层全局作用域) function test() { return this; } alert(test( ...

  2. 如何设置GridView中某个字段显示数据的一部分?

    后台方法: /// <summary> /// 截取字符串 /// </summary> /// <param name="str">要截取的字 ...

  3. 51nod算法马拉松15

    智力彻底没有了...看来再也拿不到奖金了QAQ... A B君的游戏 因为数据是9B1L,所以我们可以hash试一下数据... #include<cstdio> #include<c ...

  4. android studio安卓项目出现Error: Default Activity Not Found错误无法编译的解决方案

    项目明明是没有问题的,有时候突然就出现Error: Default Activity Not Found错误,以前出现过我重新安装了android studio 都没有用,后来在网上(http://s ...

  5. Linux 下安装中文 ctex 指南

    大家在用 $\LaTeX$ 进行中文排版时相信会遇到不少问题,而$\textbf{ctex}$套装的出现则有效的解决了这一问题,只要安装了$\textbf{ctex}$那么在文中不用进行引用设置就可以 ...

  6. 关于DOM的一些笔记(二)

    1.选择符API (1).querySelector()方法 querySelector()方法接受一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null. 通过Docu ...

  7. js通过注册表找到本地软件安装路径并且执行

    场景:用js执行本地的安装软件,如果不存在就执行安装 操作步骤: 1.前台js代码 <script type="text/javascript"> function e ...

  8. Struts2登录小例子

    前面实现了一个数据显示的例子,下面我来实现以下使用Struts2登录 首先是配置不用过多解释 注意名字要和类名保持一致 因为实现的是action这个方法所以需要用action.log来跳转到类里面 解 ...

  9. ArcGIS10的附件功能

    转自 积思园 http://blog.csdn.net/linghe301/article/details/6386176 老是忘记怎么使用这个ArcGIS10的附件功能,这次就做个记录吧. 在项目应 ...

  10. 《.NET之美》消息及勘误

    <.NET之美>消息及勘误 编辑最终还是采用了<.NET之美>作为书名,尽管我一直觉得这个名字有点文艺了,而更倾向于使用<.NET专题解析>这个名称. 目前已经可以 ...