三天前基本上把数据库表设计的文档写好,今天想到了一个问题,还要再加几个表,一个是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那么强大,我想它的附加器如此之多也是一大原因吧,基本上我们能想到的它都想到了,我们没有想到的它也想到了,下面就定义的几个具体的附加器。

aaarticlea/png;base64,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" alt="" />

附加器如何跟前面的说的日志器关联呢,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??");

打印如下信息:

aaarticlea/png;base64,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" alt="" />

这只是第一步,后面还得写数据库与邮件附加器的输出方法。写一个好的日志器还真不容易。之后会把一些可以配置的东西放到配置文件里面,

接下来就写通用类与数据库访问层。

注:需要完整源码的可以mark下,无偿发到你邮箱

step by step 之餐饮管理系统四(日志模块实现)的更多相关文章

  1. step by step 之餐饮管理系统五(Util模块)------附上篇日志模块源码

    这段时间一直在修改日志模块,现在基本上写好了,也把注释什么的都加上了,昨天邮件发送给mark的园友一直报失败,老是退回来,真是报歉,如下图所示:

  2. step by step 之餐饮管理系统七(点菜模块实现)

    好长时间没有更新这个系列了,一是因为这段时间比较忙,有很多事情,二来要学习新的东西,AngularJs,devExpress这两上框架,都是比较有名的框架,先上图: 上面就是用来点菜的界面,左边是已点 ...

  3. step by step 之餐饮管理系统二

    昨天写了餐饮管理系统的相关需求,得到了园友的一些好的建议,感到很高兴,确实写的也不全面,现在补充一下需要的业务,这次主要做的主要是前台收银系统,所以业务主要集中在前台点菜收银这块,而后面数据管理这块则 ...

  4. step byt step之餐饮管理系统一

    之前写过2015年的工作计划,其中有一项就是写一套管理系统,一来可以练练手,二来可以加强自己的学习,三来可以多园友多交流,共同进步.所以从今天开始把写系统的过程记录下来.先需求分析开始. 第一部分 引 ...

  5. 【转载】MDX Step by Step 读书笔记(四) - Working with Sets (使用集合)

    1. Set  - 元组的集合,在 Set 中的元组用逗号分开,Set 以花括号括起来,例如: { ([Product].[Category].[Accessories]), ([Product].[ ...

  6. e2e 自动化集成测试 架构 实例 WebStorm Node.js Mocha WebDriverIO Selenium Step by step (四) Q 反回调

    上一篇文章“e2e 自动化集成测试 架构 京东 商品搜索 实例 WebStorm Node.js Mocha WebDriverIO Selenium Step by step (三) SqlServ ...

  7. 课程四(Convolutional Neural Networks),第一周(Foundations of Convolutional Neural Networks) —— 2.Programming assignments:Convolutional Model: step by step

    Convolutional Neural Networks: Step by Step Welcome to Course 4's first assignment! In this assignme ...

  8. Metrics.NET step by step使用Metrics监控应用程序的性能

    使用Metrics监控应用程序的性能 在编写应用程序的时候,通常会记录日志以便事后分析,在很多情况下是产生了问题之后,再去查看日志,是一种事后的静态分析.在很多时候,我们可能需要了解整个系统在当前,或 ...

  9. 数据库设计 Step by Step (2)——数据库生命周期

    引言:数据库设计 Step by Step (1)得到这么多朋友的关注着实出乎了我的意外.这也坚定了我把这一系列的博文写好的决心.近来工作上的事务比较繁重,加之我期望这个系列的文章能尽可能的系统.完整 ...

随机推荐

  1. Multiverse in Doctor Strange // Multiverse在《神秘博士》

    关于<神秘博士>的制作内容 https://www.fxguide.com/quicktakes/bonus-luma-pictures-new-tools-for-doctor-stra ...

  2. FMDB最简单的教程-3 清空数据表并将自增字段清零

    [db executeUpdate:@"DELETE FROM MemberInfo"]; [db executeUpdate:@"UPDATE sqlite_seque ...

  3. 在php中写接口时 对json格式的转换 简单的方法

    方法 一 方法二 可以通过urlencode();遍历出来

  4. 创建一个swift项目

    笔者认为你已经有了oc的开发基础,流程是一样的,选择开发语言为swift即可.这里主要说明一下一些常用的配置: 一般我们不使用XIB和storyboard,所以在系统自动创建的文件中删除main.st ...

  5. 树莓派B+上手小记--使用HDMI线连接显示器

    入手还算比较顺利,一开始使用网上下的别人精简的OS,发现ACT及PWR灯一直亮着,上网查说用HDMI连接显示器需要修改配置文件config.txt,但修改后情况依旧. 如果还是用官方的系统试试吧,上网 ...

  6. django中自定义标签和过滤器

    想要实现自定义标签和过滤器需要进行准备工作: 准备(必需)工作: 1  在某个app下创建一个名为templatetags(必需,且包名不可变)的包.假设我们在名为polls的app下创建了一个tem ...

  7. pt-query-digest查询日志分析工具

    1.工具介绍 pt-query-digest是用于分析mysql慢查询的一个工具,它可以分析binlog.General log.slowlog,也可以通过SHOWPROCESSLIST或者通过tcp ...

  8. Icinga快速安装与配置

    Icinga快速安装与配置/* body */body { margin: 20px; padding: 0; font-family: "Lucida Grande", &quo ...

  9. Linux shell之sed

    sed编辑器逐行处理输入,然后把结果发送到屏幕. -i选项:直接作用源文件,源文件将被修改. sed命令和选项: a\ 在当前行后添加一行或多行 c\ 用新文本替换当前行中的文本 d 删除行 i\ 在 ...

  10. [INS-32025] 所选安装与指定 Oracle 主目录中已安装的软件冲突

    windows server 2008 r2 enterprise下的解决办法为:删除C:\Program Files (x86)\Oracle\Inventory\ContentsXML目录下的in ...