这里要讲的“日志”是指应用程序在运行过程中的事件记录。
应用程序在跟用户交互的过程,实质就是程序按照用户的意愿完成一件一件的事件操作的过程,然而,由于不同的交互指令,不同的环境等因素,同一事件往往会有不同的执行结果。为了监控程序的行动过程,便于发现问题中了解用户的操作行为,我们往往在程序中设计一个“日志”记录的功能,用于记录应用程序的行动状况和用户的操作过程,这样做一方面有利于设计者处理程序异常,一方面有利于软件的使用单位的管理人员了解其操作人员的操作行为,防范不良行为...
总之,为程序设计“日志”功能是十分有用和必要的。
日志的分类:
日志要以分为系统日志和操作日志两大类。系统日志是应用程序运行过程中自身发生的事件的记录,最常用的是记录系统异常以及程序运行到某些关键节点上时的记录状态。这类日志主要用于系统设计人员了解程序运行情况。而操作日志则是对用户操作行为的记录。比如某位用户的系统登录操作,用户对某行记录的修改、删除点操作。这类日志主要用于行为管理。由于这两类日志的监控对象和监控目的均不相同,因此,记录的日志的侧重点也各不相同。前者侧重于记录事件发生时的系统状况,如某些相关变量的值等,这样便于以后分析产生这一事件的原因。而后者则侧重于记录用户操作行为的具体内容,如某次记录修改前后的数据值分别是多少。
那么,一条日志记录应该记录哪些内容呢?
本人认为:一条日志记录,就是一个事件的发生,那么,我们应该能够清楚准确的描述这一事件。通常事件包含以下几个要素:人物,地点,时间以及事件内容。对于系统日志一说,其中:人物就是当时的应用程序名称,地点就是发生事件的具体代码位置,时间就是当时的系统时间,事件内容则随需要而定,关键是要能充分为设计人员提供尽可能多的现场信息。对于操作日志一说,其中:人物就是当时的操作者,地点就是发生事件的操作模块,时间就是当时的系统时间,事件内容则用户的具体操作内容,关键是要能充分反映此次操作前后的数据变化。
基于本人对“日志”的上述理解,准备自己动手,为C#设计一个较为适用的日志功能模块。
由于记录日志是在程序运行的各个时刻的事件的记录,因此,记录日志的相关代码可能分布在软件设计的各个角落,因此该模块应该便于调用才行。
首先,对于系统日志,可以用.Net框架中的EventLog类来完成。这个类可以向Windows事件查看器中增加应用程序日志记录,可以很方便的满足上述系统日志的要求,因此我选择用它来构建我的系统日志部分。
其次,对于操作日志,可以用文件或数据库表来记录。用文件记录的好处是便于查看和传递,缺点是不便于管理,易实修改或删除,因此不适合用于记录关键日志。数据库表的方法安全高效,便于查找和管理,但不便于传递。基于这两种方式各有优缺点,我准备同时实现这两各方式,在实际调用时再决定用哪种方式。
正式开始设计了:我将命名空间定义为:Lucker.LogManager,将类名定义为LogManager,并将它独立成一个项目,将来可以编译成一个单独的Dll文件,便于在其它工程中引用该Dll文件。LogManager类有分别三个构造方法:
public LogManager(string LogName),用于使用文件记录日志
public LogManager(string LogName),用于使用Windows事件查看器管理日志
public LogManager(SqlConnection con,string User),用于使用数据库表记录日志
当决定采用某种方式时,就声明相应的构造方法。如:
LogManager lm1 = new LogManager();
LogManager lm2 = new LogManager(Application.ProductName);
LogManager lm3 = new LogManager(SqlCon,"Admin");
其中,具体参数的意义见下文。LogManager类为每一种方式暴露了一个记录日志的方法,它们分别是WriteFileLog,WriteWindowsLog,WriteDBLog。每一个方法又分别有若干种重载形式,方便在不同的情况下调用。这样,就可以很方便的调用它们进行日志记录了:
lm1.WriteFileLog("log1");
lm1.WriteFileLog("type1", "log2");
...
lm2.WriteWindowsLog("log3");
lm2.WriteWindowsLog("E", "Log4");
lm2.WriteWindowsLog("source", "log5", EventLogEntryType.Error, 21, 10);
...
lm3.WriteDBLog("Test1", "Obj", "999", "99", "Just a test!");
lm3.WriteDBLog("Test2", "Obj","Just a test!");
三种方式记录日志的效果图如下:
使用Windows事件查看器管理记录日志:
使用文件记录日志,采用记事本打开:
使用数据库log表记录日志:
以下到了设计LogManager类具体实现的时候了:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace Lucker.LogManager
{
public class LogManager
{
FileStream fs;//日志文件对象流
string filepath = string.Empty;//文件保存的路径
string file = string.Empty;//文件全名(含路径)
EventLog el;//EventLog对象
SqlConnection sqlcon;//日志存放的数据库连接对象
SqlCommand sqlcom=new SqlCommand();//执行日志写入操作的命令对象
string MachineName = Environment.MachineName;//日志发生的客户端机器名
string user = string.Empty;//操作用户名
const string CreateTable = @"
CREATE TABLE [dbo].[log](
[ID] [numeric](10, 0) IDENTITY(1,1) NOT NULL,
[User] [nvarchar](20) NOT NULL,
[WorkStation] [nvarchar](50) NULL,
[DateTime] [datetime] NULL,
[LogType] [nvarchar](20) NULL,
[Object] [nvarchar](20) NULL,
[OriginalValue] [nvarchar](200) NULL,
[NewValue] [nvarchar](200) NULL,
[Remark] [nvarchar](200) NULL,
CONSTRAINT [PK_log] PRIMARY KEY CLUSTERED
(
[ID] ASC
)) ON [PRIMARY]
";//创建日志记录表的SQL语句
#region 三个构造方法
public LogManager(string LogName)
{
el = new EventLog(LogName, ".", "Default Event");//实例化一个EventLog对象,默认源为“Default Event”
}
public LogManager()
{
//文件保存在应用程序目录下的LogFiles文件夹下
filepath = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "//LogFiles//";
if (!Directory.Exists(filepath))
{
Directory.CreateDirectory(filepath);
}
//文件名为当前日期,确定每天一个日期文件,防止单个文件过大。
string filename = string.Format("{0:0000}{1:00}{2:00}.log", DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
file = filepath + filename;
}
public LogManager(SqlConnection con,string User)
{
sqlcon = con;
sqlcom.Connection = sqlcon;
//添加并初始化日志表中各个参数
sqlcom.Parameters.Add("User", SqlDbType.NVarChar, 20);
sqlcom.Parameters.Add("WorkStation", SqlDbType.NVarChar, 50);
sqlcom.Parameters.Add("DateTime", SqlDbType.DateTime);
sqlcom.Parameters.Add("LogType", SqlDbType.NVarChar, 20);
sqlcom.Parameters.Add("Object", SqlDbType.NVarChar, 20);
sqlcom.Parameters.Add("OriginalValue", SqlDbType.NVarChar, 200);
sqlcom.Parameters.Add("NewValue", SqlDbType.NVarChar, 200);
sqlcom.Parameters.Add("Remark", SqlDbType.NVarChar, 200);
user = User;
IniParameters();
}
#endregion
#region Windows log
//当指定的日志源不存在时,需要创建它
private static void CreateEventSource(string EventSourceName,string LogName)
{
if (!EventLog.SourceExists(EventSourceName))
{
try
{
EventLog.CreateEventSource(EventSourceName, LogName);
}
catch (Exception)
{
throw new Exception("Create event source failed:" + EventSourceName);
}
}
}
public void Close()
{
el.Close(); //退出程序时需要关闭对象
}
//以下是几种写Windows日志的重载方法:
public void WriteWindowsLog(string LogString)
{
el.WriteEntry(LogString);
}
public void WriteWindowsLog(string EventSourceName, string LogString)
{
CreateEventSource(EventSourceName,el.Log);
EventLog.WriteEntry(EventSourceName, LogString);
}
public void WriteWindowsLog(string EventSourceName, string LogString, EventLogEntryType LogType)
{
CreateEventSource(EventSourceName, el.Log);
EventLog.WriteEntry(EventSourceName, LogString, LogType);
}
public void WriteWindowsLog(string LogString, EventLogEntryType LogType)
{
el.WriteEntry(LogString, LogType);
}
public void WriteWindowsLog(string LogString, EventLogEntryType LogType, Int32 EventID)
{
el.WriteEntry(LogString, LogType,EventID);
}
public void WriteWindowsLog(string LogString, EventLogEntryType LogType, int EventID, short Category)
{
el.WriteEntry(LogString, LogType, EventID,Category);
}
public void WriteWindowsLog(string EventSourceName, string LogString, EventLogEntryType LogType, int EventID, short Category)
{
CreateEventSource(EventSourceName, el.Log);
EventLog.WriteEntry(EventSourceName, LogString, LogType, EventID, Category);
}
#endregion
#region File log
//以下是几种写文件日志的方法:
public void WriteFileLog(string msg)
{
try
{
fs = System.IO.File.Open(file, System.IO.FileMode.Append, System.IO.FileAccess.Write);
Byte[] info = new UTF8Encoding(true).GetBytes(string.Format("{0:yyyy-MM-dd HH:mm:ss}",DateTime.Now) + ":/r/n/t" + msg + "/r/n");
fs.Write(info, 0, info.Length);
fs.Close();
}
catch(Exception e)
{
throw new Exception("Write file log failed:" + e.Message);
}
}
public void WriteFileLog(string LogType,string msg)
{
try
{
fs = System.IO.File.Open(file, System.IO.FileMode.Append, System.IO.FileAccess.Write);
Byte[] info = new UTF8Encoding(true).GetBytes(string.Format("{0:yyyy-MM-dd HH:mm:ss}",DateTime.Now) + ":/t" + LogType + "/r/n/t" + msg + "/r/n");
fs.Write(info, 0, info.Length);
fs.Close();
}
catch (Exception e)
{
throw new Exception("Write file log failed:" + e.Message);
}
}
#endregion
#region Database log
//以下是几种写数据库日志的方法:
public void WriteDBLog(string LogType, string Object, string OriginalValue, string NewValue, string Remark)
{
sqlcom.Parameters["LogType"].Value = LogType;
sqlcom.Parameters["Object"].Value = Object;
sqlcom.Parameters["OriginalValue"].Value = OriginalValue;
sqlcom.Parameters["NewValue"].Value = NewValue;
sqlcom.Parameters["Remark"].Value = Remark;
WriteDBLog();
}
//修改数据日志:
public void WriteDBLog(string LogType, string Object, string OriginalValue, string NewValue)
{
sqlcom.Parameters["LogType"].Value = LogType;
sqlcom.Parameters["Object"].Value = Object;
sqlcom.Parameters["OriginalValue"].Value = OriginalValue;
sqlcom.Parameters["NewValue"].Value = NewValue;
WriteDBLog();
}
//对象操作日志:
public void WriteDBLog(string LogType, string Object,string Remark)
{
sqlcom.Parameters["LogType"].Value = LogType;
sqlcom.Parameters["Object"].Value = Object;
sqlcom.Parameters["Remark"].Value = Remark;
WriteDBLog();
}
//无对象操作日志:
public void WriteDBLog(string LogType, string Remark)
{
sqlcom.Parameters["LogType"].Value = LogType;
sqlcom.Parameters["Remark"].Value = Remark;
WriteDBLog();
}
//初始化日志表中各个参数
private void IniParameters()
{
sqlcom.Parameters["User"].Value = user;
sqlcom.Parameters["WorkStation"].Value = MachineName;
sqlcom.Parameters["DateTime"].Value = DateTime.Now;
sqlcom.Parameters["LogType"].Value = "Unknow";
sqlcom.Parameters["Object"].Value = "Unknow";
sqlcom.Parameters["OriginalValue"].Value = "Unknow";
sqlcom.Parameters["NewValue"].Value = "Unknow";
sqlcom.Parameters["Remark"].Value = "none";
}
private void WriteDBLog()
{
GetConnection();//检查数据库是否连接,未连接时先连接
CheckTable();//检查日志表是否存在,不存在时先创建
try
{
sqlcom.CommandText = "insert into [log]([User],[WorkStation],[DateTime],[LogType],[Object],[OriginalValue],[NewValue],[Remark]) values(@User,@WorkStation,@DateTime,@LogType,@Object,@OriginalValue,@NewValue,@Remark)";
sqlcom.ExecuteNonQuery();
IniParameters();//写入数据库后要将各参数复位
}
catch (Exception e)
{
throw new Exception("Write database log failed:" + e.Message);
}
}
private void GetConnection()
{
if(sqlcon==null)
throw new Exception("There is no SqlConnection!");
try
{
if (sqlcon.State != ConnectionState.Open)
sqlcon.Open();
}
catch (SqlException)
{
throw new Exception("Open SqlConnection failed!");
}
}
private void CheckTable()
{
try
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = sqlcon;
cmd.CommandText = "select count(*) from sysobjects where name = 'log'";
if (Convert.ToInt32(cmd.ExecuteScalar()) == 1)//log表存在
return;
else//log表不存在
{
cmd.CommandText = CreateTable;
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
throw new Exception("Table log create failed:"+ex.Message);
}
}
#endregion
}
}
由于该类考虑到了当文件路径,日志源或数据库表不存在时的情况并对它们作了相应的自动处理,因此不需要调用前作任务设置。有了该类,只需要简单的在程序的开头声明并实例化一个全局的LogManager类型的对象lm,然后就可以四处调用它了:lm.Writer**Log(××);确定十分方便易用。而且该类还实现了三个不同的方式记录日志,可以满足不同的需要。
当然,LogManager类还是很多可以进一步完善的地方,比如,对于用数据库记录日志的情况,还可以设计一个日志查看、查询的窗口界面,用XML文件来代替普通文件记录日志等。希望广大网友多多指教。