分享
 
 
 

Windows平台下程序日志的设计和实现(上:设计、实现和应用)

王朝c#·作者佚名  2006-12-17
窄屏简体版  字體: |||超大  

问题:

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有