页面中雪花飘落的效果

时间:2022-12-22 07:44:49
Windows平台下程序日志的设计和实现(上:设计、实现和应用)

 

问题:

 

 

 

 

 

       在很多应用中,需要记录程序运行过程中的关键信息、关键操作、警告和异常等。这些信息可以被用来追溯、调试和排错、分析运行时环境,或者用于其他特定的用途。一些长期运行的、无人监控的或者运行在后台不带UI的程序,记录运行时的日志尤其重要。  

 

目标:

 

 

 

 

 

1、  实现日志记录功能。

 

 

 

 

 

 

2、  模块容易使用、容易扩展、可灵活配置。

 

 

 

 

 

 

3、  日志内容分类输出。  

 

约束:

 

 

 

 

 

Windows平台 .net 架构  

 

方案:

 

 

 

 

 

1、  可以向文件、数据库和Windows事件日志中记录日志(打开Windows的“控制面板/管理工具/事件查看器”,系统显示Windows事件日志),并通过配置文件灵活配置。

 

 

 

 

 

 

2、  可以容易地增加其他的日志输出源。

 

 

 

 

 

 

3、  写入日志源的同时,可以把控制“委托(delegate)”给外部入口。通常用这个办法通知监控程序。例如,在写日志的同时,将日志信息“委托”输出到用户界面的一个ListBox中,管理员可以在ListBox中适时看到日志信息。

 

 

 

 

 

 

4、  日志分类输出:DEBUG < INFO < WARN < ERROR < FATAL。小于预定级别的日志不输出。  

 

设计:

 

 

 

 

 

1、设计类图: 

 

 

 

 

 

 

2、设计思路:

名称

 

 

 

 

 

 

类型

 

 

 

 

 

 

功能描述

 

 

 

 

 

 

ILogger 

 

 

 

 

 

 

接口

 

 

 

 

 

 

日志模块的接口,对外约定了操作日志的公共方法。

 

 

 

 

 

 

CLoggerAbstract

 

 

 

 

 

 

抽象类

 

 

 

 

 

 

CLoggerAbstract 实现接口 ILogger。

 

 

 

 

 

 

在 CLoggerAbstract 中实现了日志操作的通用方法,是日志实现的框架。向具体输出源写入日志的操作通过子类实现CLoggerAbstract的抽象函数来实现。

 

 

 

 

 

 

这是一个典型的使用继承的Template模式(Template模式的另外一种实现方法是使用聚合)。

 

 

 

 

 

 

CLoggerAbstract中包含一个List,用以记录所有向日志登记的外部入口函数。一旦写日志的动作发生,CLoggerAbstract调用所有登记的入口函数,并把日志信息作为参数传入。这是一个观察者模式。

 

 

 

 

 

 

CLoggerFil

 

 

 

 

 

 

CLoggerDatabase

 

 

 

 

 

 

CLoggerEvent

 

 

 

 

 

 

 

 

 

 

 

 

分别继承自抽象类CLoggerAbstract,它们分别实现了向文件、数据库和Windows事件日志写入日志的具体操作。

 

 

 

 

 

 

上述三个类和CLoggerAbstract 类构成了模板模式。

 

 

 

 

 

 

CLoggerFactory

 

 

 

 

 

 

 

 

 

 

 

 

提供静态方法创建具体的日志处理类(CLoggerFile、CLoggerDatabase、CLoggerEvent)。

 

 

 

 

 

 

这是一个工厂模式。  

 

实现:

 

 

 

 

 

       关键部分的实现。

 

 

 

 

 

 

1、  接口ILogger

 

 

 

 

 

 

     public interface ILogger

 

 

 

 

 

 

     {

 

 

 

 

 

 

         // 写日志

 

 

 

 

 

 

         void debug( string message, string source );

 

 

 

 

 

 

         void info ( string me

ssage, string source );

 

 

 

 

 

 

         void warn ( string message, string source );

 

 

 

 

 

 

         void error( string message, string source );

 

 

 

 

 

 

         void fatal( string message, string source );  

 

         // 设置和返回日志输出级别

 

 

 

 

 

 

         void setLevel(enumLevel level);

 

 

 

 

 

 

         enumLevel getLevel();  

 

         // 管理外部程序处理入口

 

 

 

 

 

 

         void addEntryWrittenHander( Delegate handler );

 

 

 

 

 

 

         void clearEntryWrittenHander();

 

 

 

 

 

 

         void enableEntryWrittenHander( bool enabled );

 

 

 

 

 

 

     }  

 

2、  模板方式的实现

 

 

 

 

 

 

在抽象类CLoggerAbstract中声明了抽象方法:  

 

         // 抽象函数,具体的写日志操作由子类实现

 

 

 

 

 

 

         public abstract void _writeLog(string message, string source, enumLevel type);  

 

CLoggerAbstract中写日志的代码(以debug()为例): 

 

         if( _outputLevel <= enumLevel.DEBUG ) // 实现日志分级输出

 

 

 

 

 

 

              write( message, source, enumLevel.DEBUG );  

 

CLoggerAbstract的write()实际调用了抽象函数_writeLog():  

 

         private void write( string message, string source, enumLevel type )

 

 

 

 

 

 

         {

 

 

 

 

 

 

              try

 

 

 

 

 

 

              {

 

 

 

 

 

 

                  _writeLog( message, source, type ); // 调用抽象方法

 

 

 

 

 

                   OnEntryWritten( message, source, type );

 

 

 

 

 

 

              }

 

 

 

 

 

 

              catch( Exception ex )

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   Trace.WriteLine("Write logger error:" + ex.Message);

 

 

 

 

 

 

              }

 

 

 

 

 

 

         }  

 

抽象函数_writeLog()的具体实现在各个子类中,因日志输出源的不同而有不同的版本。整个的运算逻辑在模板类中(这里是抽象类CLoggerAbstract),具体类只是填充了模板类的空白。这样做,达到了代码复用的目的,并且使业务逻辑更加清晰。 

 

3、  管理外部处理入口函数

 

 

 

 

 

 

CLoggerAbstract中包含一个List,用来保存所有登记的外部处理入口函数:  

 

         // 写日志时触发外部方法的HANDLE

 

 

 

 

 

 

         private static ArrayList _entryWrittenEventHandler = new ArrayList();  

 

当CLoggerAbstract写入日志的时候,依次调用_entryWrittenEventHandler中登记的函数,并把当前写入的日志内容作为参数传给这些函数:  

 

     if( _entryWrittenEventHandler.Count >0 )

 

 

 

 

 

 

     {

 

 

 

 

 

 

         myLogEntryWrittenArgs args = new myLogEntryWrittenArgs();

 

 

 

 

 

 

         args.Entry.TimeWritten = DateTime.Now;

 

 

 

 

 

 

         args.Entry.Message     = message;

 

 

 

 

 

 

         args.Entry.Type        = type;

 

 

 

 

 

 

         args.Entry.Source      = source;

 

 

 

 

 

 

         foreach( myLogEntryWrittenHandler aHandler in _entryWrittenEventHandler )

 

 

 

 

 

 

         {

 

 

 

 

 

 

              aHandler( this, args );

 

 

 

 

 

 

         }

 

 

 

 

 

 

     }

 这样一来,凡是在CLoggerAbstract中登记的函数都能够适时得到写入日志的信息。CLoggerAbstract提供了一组方法,用以登记和取消登记。

 

 

 

 

 

 

         /// <summary>

 

 

 

 

 

 

         /// 设置写日志时触发的外部方法

 

 

 

 

 

 

         /// </summary>

 

 

 

 

 

 

         /// <param name="handler">外部方法HANDLE</param>

 

 

 

 

 

 

         public void addEntryWrittenHander( Delegate handler )

 

 

 

 

 

 

         {

 

 

 

 

 

 

              try

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   if( !_entryWrittenEventHandler.Contains(handler) )

 

 

 

 

 

 

                       _entryWrittenEventHandler.Add( handler );

 

 

 

 

 

 

              }

 

 

 

 

 

 

              catch( Exception ex )

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   this.warn("add entry written hander error:"+ex.Message, this.ToString());

 

 

 

 

 

 

              }

 

 

 

 

 

 

         }  

 

         /// <summary>

        /// 清除写日志时触发的外部方法

 

 

 

 

 

 

         /// </summary>

 

 

 

 

 

 

         public void clearEntryWrittenHander()

 

 

 

 

 

 

         {

 

 

 

 

 

 

              try

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   _entryWrittenEventHandler.Clear();

 

 

 

 

 

 

              }

 

 

 

 

 

 

              catch( Exception ex )

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   this.warn("clear entry written hander error:"+ex.Message, this.ToString());

 

 

 

 

 

 

              }

 

 

 

 

 

 

         }  

 

4、  CLoggerFactory

 

 

 

 

 

 

CLoggerFactory中定义了一个静态方法,用来根据配置文件中的配置项创建不同的日志处理类。

 

 

 

 

 

 

         public static ILogger getLogger()

 

 

 

 

 

 

         {

 

 

 

 

 

 

              // 日志类型

 

 

 

 

 

 

              string logger = CConfiguration.getConfiguration("logger.type");

 

 

 

 

 

 

              // 日志名或日志文件名

 

 

 

 

 

 

              string loggerName = CConfiguration.getConfiguration("logger.name");

 

 

 

 

 

 

              // 日志级别

 

 

 

 

 

 

              enumLevel level = str2enum( CConfiguration.getConfiguration("logger.level") );  

 

              // 创建日志类

 

 

 

 

 

 

              if( logger.ToLower().Equals("file") ) // 文件日志

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   return new CLoggerFile( loggerName, level );

 

 

 

 

 

 

              }

 

 

 

 

 

 

              else if( logger.ToLower().Equals("database") ) // 数据库日志

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   return new CLoggerDatabase( loggerName, level );

 

 

 

 

 

 

              }

 

 

 

 

 

 

              else// if( logger.ToLower().Equals("event") ) // 缺省使用系统日志

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   return new CLoggerEvent( loggerName, level );

              }

 

 

 

 

 

 

         }  

 

应用:

 

 

 

 

 

1、  配置文件

 

 

 

 

 

 

l         在配置文件(开发环境中名为app.config文件)中修改logger.type的值,可以指定日志输出源。database代表数据库;file代表文件;event或者其他代表Windows系统日志。

 

 

 

 

 

 

l         在配置文件中修改logger.level的值,可以指定日志的输出级别。日志级别:DEBUG < INFO < WARN < ERROR < FATAL。

 

 

 

 

 

 

l         在配置文件中修改logger.name的值,则对应不同的logger.type可以有不同的含义。

 

 

 

 

 

 

1.       如果logger.type是database,则logger.name是向数据库写日志的SQL串,通常是一条带参数的INSERT语句;

 

 

 

 

 

 

2.       如果logger.type是file,则logger.name是记录日志的文件名;

 

 

 

 

 

 

3.       如果logger.type是event,则logger.name是向Windows写入日志的源名。  

 

2、  设置例子

 

 

 

 

 

 

日志源为数据库,日志输出级别为:DEBUG,记录日志的数据库表(oracle)已经建立。则配置文件:

 

 

 

 

 

 

<add key="database.connectionString"

 

 

 

 

 

 

     value="Provider=OraOLEDB.Oracle.1;Password=zy;User ID=zy;Data Source=mytest" />

 

 

 

 

 

 

         ……

 

 

 

 

 

 

<add key="logger.type" value="database" />

 

 

 

 

 

 

          <add key="logger.level" value="debug" />

 

 

 

 

 

 

          <add key="logger.name" value="begin insert into logger(sydate, message, source,

>

 

 

              loglevel) values(´{0}´,´{1}´,´{2}´,´{3}´); commit;end;" />  

 

日志源为文件,日志输出级别为:DEBUG。

 

 

 

 

 

 

     <add key="logger.type" value="file" />

 

 

 

 

 

 

     <add key="logger.level" value="debug" />

 

 

 

 

 

 

         <add key="logger.name" value="c://mylog2005.log" />  

 

日志源为系统,日志输出级别为:INFO。

 

 

 

 

 

 

     <add key="logger.type" value="event" />

 

 

 

 

 

 

     <add key="logger.level" value="info" />

 

 

 

 

 

 

         <add key="logger.name" value="myevent" />  

 

3、  应用举例:  

 

定义一个logger

 

 

 

 

 

 

         private static ILogger logger = CLoggerFactory.getLogger();

 

 

 

 

 

 

在Form上添加Listbox,定义外部处理函数:

 

 

 

 

 

 

         public void logEntryWritten(object sender, myLogEntryWrittenArgs e)

 

 

 

 

 

 

         {

 

 

 

 

 

 

              try

 

 

 

 

 

 

              {

                   listBox1.Items.Insert( 0, e.Entry.TimeWritten + " " + e.Entry.Message );

 

 

 

 

 

 

                   //最多显示500行

 

 

 

 

 

 

                   while( listBox1.Items.Count > 500 )

 

 

 

 

 

 

                   {

 

 

 

 

 

 

                       listBox1.Items.RemoveAt( listBox1.Items.Count-1 );

 

 

 

 

 

 

                   }

 

 

 

 

 

 

              }

 

 

 

 

 

 

              catch( Exception ex )

 

 

 

 

 

 

              {

 

 

 

 

 

 

                   MessageBox.Show( ex.Message );

 

 

 

 

 

 

              }

 

 

 

 

 

 

         }

 

 

 

 

 

 

将外部处理函数向Logger登记:

 

 

 

 

 

 

         logger.clearEntryWrittenHander();

 

 

 

 

 

 

         logger.addEntryWrittenHander( new myLogEntryWrittenHandler(this.logEntryWritten) );

 

 

 

 

 

 

     写日志:

 

 

 

 

 

 

         _log.info( "启动 Schedule" ,this.ToString());

 

 

 

 

 

 

    每当写入日志的时候,logEntryWritten()会被调用。

扩充:

 

 

 

 

 

       可以在以下方面对程序进行扩充:

 

 

 

 

 

 

1、  日志内容的格式化输出;

 

 

 

 

 

 

2、  增加多种日志输出源

 

 

 

 

 

 

3、  更强的运行时信息输出,如写入日志的类、函数、行号等信息。

 

 

作者:张昱(e-zhangyu@vip.sina.com)