三天前基本上把数据库表设计的文档写好,今天想到了一个问题,还要再加几个表,一个是log表,用来记录系统日志,另外再加几个字典表,一些需要配置的数据但又不好放在像xml文件里面的数据可以放在这些字典表里面。
从今天开始就正式进入系统设计与编码了,详细设计文档等系统做好后再补充了,因为一开始全部写好不大现实,中间过程中会不断地去迭代。现在的想法是每个模块分别去实现,然后再分别记录下来。
今天要写的是日志模块,因为在生产环境中,好的日志至于重要,系统运行时出现的任何问题可以通过日志记录下来,对于发现与解决问题非常有帮助。因为日志是一个相对比较通用的模块,所以先设计好如果写日志模块,之后再写通用类模块,再数据库访问层与用户自定义控件,然后再数据实体与业务处理层,最后再写用户表现层。
因为此次不使用第二方控件,所以不考虑像log4net,微软enterprise中好的日志控件,但是看了它们的代码,总体思想都差不多,就是各种级别的日志信息应该以什么样的格式输出到哪种介质中,也就是输出源,像文本文件还是数据库,还是控制台,还是系统日志邮件等等。基于它们的思想,把构思记录下来:
日志最主要的就是一个日志器与附加器,像log4net中可以定义多个日志器,一个日志器可以附加多个输出源,而日志仓库就是如何存储和管理日志器,过虑器如果过虑各种级别的日志,而layout就是如何显示输出的消息。
1、输出源只包含文本文件,数据库,系统日志和邮件。
2、日志级别分别为Fatal, Error, Warn, Info, Debug。
还是拿代码为例子来讲吧,首先定义一个日志器接口与日志操作接口ILog,日志器得先有一个名字,它的方法就是一个Log和IsEnabledFor,ILog包含的方法如下,用过log4net等日志控件的应该很熟悉。
日志器接口详细代码如下:
public interface ILogger { /// <summary> /// Gets the name of the logger. /// </summary> string Name { get; } /// <summary> /// This generic form is intended to be used by wrappers. /// </summary> void Log(LogCategory level, object message, Exception exception); bool IsEnabledFor(LogCategory level); }
然后再定义一个包装接口
public interface ILoggerWrapper { ILogger Logger { get; } }
定义ILog接口,最后调用的都是在这里定义的接口
public interface ILog : ILoggerWrapper { void Debug(object message); void Debug(object message, Exception exception); void DebugFormat(string format, params object[] args); void Info(object message); void Info(object message, Exception exception); void InfoFormat(string format, params object[] args); void Warn(object message); void Warn(object message, Exception exception); void WarnFormat(string format, params object[] args); void Error(object message); void Error(object message, Exception exception); void ErrorFormat(string format, params object[] args); void Fatal(object message); void Fatal(object message, Exception exception); void FatalFormat(string format, params object[] args); bool IsDebugEnabled { get; } bool IsInfoEnabled { get; } bool IsErrorEnabled { get; } bool IsWarnEnabled { get; } bool IsFatalEnabled { get; } }
日志器有了,可以附加器呢,也就是源出源,其实真正的任务都是委托这些具体的附加器去做的呢,为什么log4net那么强大,我想它的附加器如此之多也是一大原因吧,基本上我们能想到的它都想到了,我们没有想到的它也想到了,下面就定义的几个具体的附加器。
附加器如何跟前面的说的日志器关联呢,20个附加器不可能都直接与日志器去关联吧,所以定义一个所有附加器要实现的接口IAppender.
public interface IAppender { string Name { get; set; } void Close(); void DoAppender(LogCategory level, object message, Exception exception); }
拿文本文件为例,日志的输出源就是文本文件,消息写到这个介质上,下面是一个基类,然后子类就是继承这个类实现Write方法去写日志信息:
public class TextWriterAppender : TextWriter { private TextWriter m_writer; public TextWriter Writer { get { return m_writer; } set { m_writer = value; } } virtual public string Name { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } #region Public Methods /// <summary> /// Closes the writer and releases any system resources associated with the writer /// </summary> /// <remarks> /// <para> /// </para> /// </remarks> override public void Close() { m_writer.Close(); } /// <summary> /// Dispose this writer /// </summary> /// <param name="disposing">flag indicating if we are being disposed</param> /// <remarks> /// <para> /// Dispose this writer /// </para> /// </remarks> override protected void Dispose(bool disposing) { if (disposing) { ((IDisposable)m_writer).Dispose(); } } /// <summary> /// Flushes any buffered output /// </summary> /// <remarks> /// <para> /// Clears all buffers for the writer and causes any buffered data to be written /// to the underlying device /// </para> /// </remarks> override public void Flush() { m_writer.Flush(); } /// <summary> /// Writes a character to the wrapped TextWriter /// </summary> /// <param name="value">the value to write to the TextWriter</param> /// <remarks> /// <para> /// Writes a character to the wrapped TextWriter /// </para> /// </remarks> override public void Write(char value) { m_writer.Write(value); } /// <summary> /// Writes a character buffer to the wrapped TextWriter /// </summary> /// <param name="buffer">the data buffer</param> /// <param name="index">the start index</param> /// <param name="count">the number of characters to write</param> /// <remarks> /// <para> /// Writes a character buffer to the wrapped TextWriter /// </para> /// </remarks> override public void Write(char[] buffer, int index, int count) { m_writer.Write(buffer, index, count); } /// <summary> /// Writes a string to the wrapped TextWriter /// </summary> /// <param name="value">the value to write to the TextWriter</param> /// <remarks> /// <para> /// Writes a string to the wrapped TextWriter /// </para> /// </remarks> override public void Write(String value) { m_writer.Write(value); } public override Encoding Encoding { get { return m_writer.Encoding; } } #endregion }
日志器与附加器都有了,怎么去连接它们了,最后我想还是用泛型比较灵活,定义如下:
public class LogFactory<L, A> : ILog where L : ILogger, new() where A : IAppender, new() { virtual public void Debug(object message) { #if DEBUG Logger.Log(m_levelDebug, message, null); #endif } virtual public void Debug(object message, Exception exception) { #if DEBUG Logger.Log(m_levelDebug, message, exception); #endif } virtual public void DebugFormat(string format, params object[] args) { #if DEBUG if (IsDebugEnabled) { Logger.Log(m_levelDebug, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } #endif } virtual public void Info(object message) { Logger.Log(LevelInfo, message, null); } virtual public void Info(object message, Exception exception) { Logger.Log(LevelInfo, message, exception); } virtual public void InfoFormat(string format, params object[] args) { if (IsInfoEnabled) { Logger.Log(LevelInfo, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public void Warn(object message) { Logger.Log(LevelWarn, message, null); } virtual public void Warn(object message, Exception exception) { Logger.Log(LevelWarn, message, exception); } virtual public void WarnFormat(string format, params object[] args) { if (IsWarnEnabled) { Logger.Log(LevelWarn, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public void Error(object message) { Logger.Log(LevelError, message, null); } virtual public void Error(object message, Exception exception) { Logger.Log(LevelError, message, exception); } virtual public void ErrorFormat(string format, params object[] args) { if (IsErrorEnabled) { Logger.Log(LevelError, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public void Fatal(object message) { Logger.Log(LevelFatal, message, null); } virtual public void Fatal(object message, Exception exception) { Logger.Log(LevelFatal, message, exception); } virtual public void FatalFormat(string format, params object[] args) { if (IsFatalEnabled) { Logger.Log(LevelFatal, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null); } } virtual public bool IsDebugEnabled { get { return Logger.IsEnabledFor(m_levelDebug); } } virtual public bool IsInfoEnabled { get { return Logger.IsEnabledFor(m_levelInfo); } } virtual public bool IsErrorEnabled { get { return Logger.IsEnabledFor(m_levelError); } } virtual public bool IsWarnEnabled { get { return Logger.IsEnabledFor(m_levelWarn); } } virtual public bool IsFatalEnabled { get { return Logger.IsEnabledFor(m_levelFatal); } } private LogCategory m_levelDebug; public LogCategory LevelDebug { get { return LogCategory.Debug; } set { m_levelDebug = LogCategory.Debug; } } private LogCategory m_levelInfo; public LogCategory LevelInfo { get { return LogCategory.Info; } set { m_levelInfo = LogCategory.Info; } } private LogCategory m_levelWarn; public LogCategory LevelWarn { get { return LogCategory.Warn; } set { m_levelWarn = LogCategory.Warn; } } private LogCategory m_levelError; public LogCategory LevelError { get { return LogCategory.Error; } set { m_levelError = LogCategory.Error; } } private LogCategory m_levelFatal; public LogCategory LevelFatal { get { return LogCategory.Fatal; } set { m_levelFatal = LogCategory.Fatal; } } public ILogger Logger { get { return new L(); } } }
把上面的泛型类闭合一个日志类:
public class LogBase<A> : LogFactory<LogBase<A>, A>, ILogger where A : IAppender, new() { private LogCategory m_logLevel; public string Name { get { return "LogBase"; } } private A m_instance; public A Instance { get { if (m_instance == null) { m_instance = new A(); } return m_instance; } set { m_instance = value; } } public void Log(LogCategory level, object message, Exception exception) { Instance.DoAppender(level, message, exception); } public bool IsEnabledFor(LogCategory level) { switch (level) { case LogCategory.Fatal: LevelFatal = LogCategory.Fatal; break; case LogCategory.Error: LevelError = LogCategory.Error; break; case LogCategory.Warn: LevelWarn = LogCategory.Warn; break; case LogCategory.Debug: LevelDebug = LogCategory.Debug; break; case LogCategory.Info: LevelInfo = LogCategory.Info; break; default: m_logLevel = LogCategory.Info; break; } return true; } }
再关闭一个泛型参数:
public class TxtFileLog : LogBase<TxtFileLog>, IAppender { private string m_name; private FileLogWriter m_writer; public TxtFileLog() { if (m_writer == null) { m_writer = new FileLogWriter(); } } public FileLogWriter Writer { get { return m_writer; } set { m_writer = value; } } public new string Name { get { return m_name; } set { m_name = value; } } public void Close() { m_writer.Close(); } public void DoAppender(LogCategory level, object message, Exception exception) { m_writer.Write(Convert.ToString(message), level, (LogMessage)exception); }
上面的类实现了日志器与附加器的连接,然后就可以去客户端验证好不好用了:
ILog log = new TxtFileLog(); log.Debug("Are you OK??");
打印如下信息:
RIAIkYS4jsLG51HuzX2cqzIw2RmpuT67qSDZFRbsYR+1HWUdaP+JeNhw+RmtlLX6/AvxW4Nla6NO/3u/KFix4iS4s41QcAQWCIXNg4HUmI1H9bEOum6x0ijV+6KBsQbpzyEdcbuWvUFkrZjowotMn7qbxmjHU0/eh3KdkQ6RoKNeGpllM6b9/+4hoFhkj9de4RIjULiX79R0mfANIUHiKzHBkUImv3DT+5oHjWNt54XCheHJn/Qa5f2oxr85r+ZU71hQqlWzeiOsJB/XFjnej7rK9QK3BqtZa7LOUl6/s3dpgVGZrCOK+tH80+D63Ltgr9jsnrygYXGzCWkvfZNpEwu20flGsRNtl4VihbrFO+NgD436SESKDSPHLG1HfiRwiRFULbAFIQFCJPbubX219y5AYhEkAM1c3BFW07U93OAYxFSIjcObn+6dPJ/Hp7Yb29sHFKiAQAAEhFUIg8vv7xt+MsR3YJkQAAAIkICZGN46tXH1tZjiREAgAApCIwRM6tHREiAQAAkhMSIrdbly8/HGZfRhIiAQAAUhEUIo8uZ98fZDmye0OIBAAASENIiPzzqPditTn7/mBu7ejVx1b35l9CJAAAQBKCQuRhb2Zlf3b14OWHw7m1I0IkAABAKkJC5B+H3R9+/XtmZX/wS+0LQiQAAEAiAkPk97/8leVIQiQAAEAqAkPkd+/2BjnyxWrz4poQCQAAkIaQEPn7wcXzt7uDHDmzsk+IBAAASEVgiHz2Zuf5293Bl5EX1/8QIgEAAJIQJUQOvow8J0QCAAAkIjBE1pcbWY4kRAIAAKQiKEQ2z+tLjSxHnl8RIgEAANIQGCKfLm3Xl7/kyPOrO0IkAABAEsJDZJYjCZEAAACpIEQCAADAWWiI/Hn76dL24P+M7BAiAQAAEhHlm8jBH0IkAABAKgiRAAAAcEaIBAAAgDNCJAAAAJwRIgEAAOCMEAkAAABngf92NiESAAAgRYRIAAAAOAsMkfXlBiESAAAgOeEhsr7cqC81CJEAAAAJiRMilxv1Jf7ZQwAAgGQQIgEAAOAsMEQ+e7OT5Ug5RP4HI16pat8qjYgAAAAASUVORK5CYII=" alt="" />
这只是第一步,后面还得写数据库与邮件附加器的输出方法。写一个好的日志器还真不容易。之后会把一些可以配置的东西放到配置文件里面,
接下来就写通用类与数据库访问层。
注:需要完整源码的可以mark下,无偿发到你邮箱