蛙蛙推荐:设计一个Tracing组件
【需求】
wawaKM的数据访问,性能计数器,主键生成的公共组件都做好了,为了便于以后排查问题的方便,还得做一个Tracing的公共组件,罗列需求如下:
1、能把跟踪信息记录到各种记录器里,默认要实现文本和数据库的记录;
2、要有一个能记录EventLog的接口,以便记录重要的系统级别的跟踪信息;
3、记录器可以扩展,比如实现一个UDP记录器,以便可以在其它机器实时查看trace;
4、Tracing组件不能影响主业务性能;
5、跟踪信息可以分级别,Info,Warn,Error
6、可以单独给不同级别的跟踪信息配置记录器,比如所有Error的日志记录到一个SMTP的记录器里发到指定的收件箱
7、跟踪信息可以分类别,便于以后查找某类别的跟踪信息
8、跟踪信息要默认把当前所在的机器IP,所属类,进程、线程、AppDomain,时间等信息记录下来。
9、修改跟踪相关配置后即时生效,无需重启应用
【配置】
根据需求,指定配置文件大约如下
<
WawaTrace
category
="login;sync"
enabled
="true"
enabled_system_trace
="false"
enabled_web_trace
="true"
>
<
TextAppender
enabled
="true"
level_filter
="*"
cate_filter
="*"
console
="true"
trace_path
=""
></
TextAppender
>
<
DbAppender
enabled
="true"
Level_filter
="error"
cate_filter
="login"
connstr
=""
></
DbAppender
>
<
UDPAppender
enabled
="true"
enable_ip_filter
="true"
>
<
Observer
ip
="192.168.0.250"
user
="大碗豆浆"
pwd
="@_@-_-"
/>
<
Observer
ip
="192.168.0.111"
user
="千山一鸟"
pwd
="-_-@_@"
/>
</
UDPAppender
>
</
WawaTrace
>
解释一下,
WawaTrace节点:category表示跟踪类别,多个分类用英文半角分号隔开;enabled表示是否启用跟踪,可取true和false。enabled_system_trace是否捕获System.Diagnostics.Trace的跟踪信息;enabled_web_trace写日志的时候是否同时也写入web的跟踪里。
TextAppender节点:enabled表示是否启用文本记录器,level_filter表示跟踪级别过滤器,如选*表示记录所有级别trace,如选error即只记录error级别的日志,可取*,error,warn,debug,info;cate_filter表示跟踪类别过滤器,可以取WawaTrace的category属性的值和*值,多个值用英文半角分号隔开,表示本记录器只记录某些类别的跟踪信息;console表示在输出到文本的时候是否同时输出到标注控制台输出;trace_path表示文本日志保存的目录。
DbAppender节点:enabled,Level_filter,cate_filter同TextAppender,connstr表示日志记录的数据库连接字符串,其中表名和字段名都应该是事先约定好的。
UDPAppender节点:enabled,Level_filter,cate_filter同TextAppender,enable_ip_filter属性表示是否启用IP过滤,如果不启用IP过滤,跟踪观察者只要有用户名和密码就可以查看跟踪信息。
UDPAppender/Observer节点:ip表示观察者所在的机器IP,当启用了enable_ip_filter的时候才起作用,user和pwd分别表示该ip上所允许的观察者的用户名和密码。
【接口设计】
要开发一个组件或者类库,要先要从使用者的角度去考虑,想想他们怎么去用你的组件感觉比较舒服,所以要先设计好用户的使用接口,考虑有哪些用户使用的场景。
1、获取一个默认跟踪器实例
static ITraceing _tracer = TracerFactory.GetTracer(typeof(MyClass));
其中MyClass是使用Tracer的类名
2、获取一个带类别的跟踪器实例
static ITraceing _tracer = TracerFactory.GetTracer(typeof(MyClass), MyTraceCate.Login);
其中MyTraceCate是一个保存所有类别信息的类,里面只有一些常量。
3、记录跟踪信息
_trace.Info("this is a test trace"); //记录一条日志
_trace.Info("{0}是个大坏蛋", name); //用string.Format记录日志
_trace.Error(ex,"here has a error"); //记录一个异常信息,其中ex是一个异常
4、记录一条系统日志
SystemEventLog.Info(GlobalUiti.ApplicationName, "application ia start"); //记录一条消息级别的系统日志
SystemEventLog.Wran(ex,GlobalUiti.ApplicationName, "application has an error"); //记录一条警告级别的系统日志,其中ex是一个异常
其中GlobalUiti.ApplicationName充当eventlog的事件源,SystemEventLog做成一个静态类
用户接口设计要尽量简单直观,方便使用,好的组件就应该是使用简单,配置灵活,好了,下一步,设计大框架。
【概要设计】
一般概要设计可以用文字描述,UML图,伪代码等来表达,其中伪代码最有表达力,不用像UML还要生成代码,也不用像文字描述那样有二义性,我们先用伪代码表达一下概要设计,然后再详细说一说每个类,代码如下
user interface and imp
#region user interface and imp
public interface ITracer
{
void Info();
void Debug();
void Warn();
void Error();
}
sealed class TracerImp : ITracer
{
List<ITraceWriter> _writers = new List<ITraceWriter>();
ITracer 成员#region ITracer 成员
public void Info()
{
throw new Exception("The method or operation is not implemented.");
}
public void Debug()
{
throw new Exception("The method or operation is not implemented.");
}
public void Warn()
{
throw new Exception("The method or operation is not implemented.");
}
public void Error()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
public class TracerFactory
{
public static ITracer GetTrace(Type type) { return null; }
public static ITracer GetTrace(string catetory, Type type) { return null; }
}
#endregion
enum and entity class
#region enum and entity class
public enum TracingLevel
{
Info = 1,
Debug = 2,
Warn = 3,
Error = 4
}
class TraceItem
{
public string MachineName;
public string ProcessInfo;
public DateTime Time;
public string Message;
public TracingLevel Level;
public Exception Error;
public string Category;
}
#endregion
TraceAppender
#region TraceAppender
abstract class ITraceWriter
{
public bool Enabled;
public string LevelFilter;
public List<string> CateFilter;
public abstract void WriterTrace(TraceItem item);
}
class TextTraceWriter : ITraceWriter
{
ITraceWriter 成员#region ITraceWriter 成员
public override void WriterTrace(TraceItem item)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
class DbTraceWriter : ITraceWriter
{
ITraceWriter 成员#region ITraceWriter 成员
public override void WriterTrace(TraceItem item)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
class UDPTraceWriter : ITraceWriter
{
ITraceWriter 成员#region ITraceWriter 成员
public override void WriterTrace(TraceItem item)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
#endregion
Config
#region Config
class TraceConfig
{
public List<string> Categories;
public bool Enabled;
public bool EnabledSystemTrace;
public bool EnabledWebTrace;
public class TextAppender
{
public bool Enabled;
public string LevelFilter;
public List<string> CateFilter;
public bool EnabledConsole;
public string LogPath;
}
public class DbAppender
{
public bool Enabled;
public string LevelFilter;
public List<string> CateFilter;
public string Connstr;
}
public class UDPAppender
{
public bool Enabled;
public string LevelFilter;
public List<string> CateFilter;
public bool Enable_Ip_Filter;
public List<TraceObserver> Observers;
}
public class TraceObserver
{
public string IP;
public string User;
public string Pwd;
}
}
#endregion
SystemTrace
#region SystemTrace
public static class SystemEventLog
{
public static void Info(string eventSource, string message, params object[] objs) { }
public static void Warn(Exception ex, string eventSource, string message, params object[] objs) { }
public static void Error(Exception ex, string eventSource, string message, params object[] objs) { }
}
#endregion
Utilities
#region Utilities
static class MonitorUtilities
{
public static string ExpandStackTrace(Exception ex) { return null; }
public static string GetMachineName() { return null; }
public static string GetProcessInfo() { return null; }
public static string SafeFormat(string format, params Object[] formatArgs) { return null; }
}
#endregion
说明:
1、ITracer是用户使用的接口类,用户用它来进行日志记录,里面写了几个原型方法,当然还不全,每个方法应该都有一系列的重再方法。TracerImp是ITracer的实现,里面调用各种TraceWriter来写日志,这里面要用一个队列来缓存要记录的跟踪信息,然后用一个线程来论询扫描队列进行日志记录,这样可以防止直接对硬盘、数据库、网络的集中写操作。TracerFactory里有两个工厂方法,用来方便用户使用日志记录器,该类可以声明为静态类。
2、TracingLevel是跟踪级别,这里用枚举来表示,真实情况下应该用一个实现IComparable接口的类(有待推敲)。TraceItem表示一条跟踪消息,包含了一条跟踪消息所有的信息。
3、ITraceWriter是一个抽象类(命名成一个接口了,回头再改),它表示一个日志记录器,用它来把跟踪消息记录到各种存储器上,分别有跟踪级别,跟踪类别,是否启用几个属性,以及记录日志的方法。TextTraceWriter,DbTraceWriter,UDPTraceWriter分别是ITraceWriter的三个实现,分别实现文本,数据库和UDP的记录。
4、TraceConfig用来读取和缓存配置信息,对应于上面的XML配置,这里用了好多嵌套类,再详细设计的时候最好不要这样用。
5、SystemEventLog封装了EventLog的操作,让用户可以轻松访问系统日志。
6、MonitorUtilities是一个帮助类,用来把一个异常的详细信息,包括内部异常的信息,获取本机IP,当前线程,进程,应用程序域等信息,以及以安全的方式(不像string.format会抛出异常)格式化一个字符串。
【小节】
概要设计和伪代码有了,就要写测试用例和实现具体逻辑的代码了,测试用例就针对用户接口编写就行了。具体类的编码上就要考虑算法的细节,性能这些了,写的时候我再把细节整理一下,先写到这儿。
不用劝我有entlib.loger,log4net,nlog啥的这些东西,没必要自己实现一个,我就是闲着没事写着玩儿,自己写的是自己的,而且灵活,简单,自己懂,出错了自己好查。
本文主要写一个组件或者类库从需求到设计的这么一个过程,不涉及详细的技术细节,我接下来会实现这个设计。