开始着手写这个系列文章,主要原因是想记录下自己思考的过程,另外如果能为有这个爱好的同仁提供一点点的思路,就十分的值得了。
架构这个东西在多年前被捧上神坛,似乎架构师是多么一件光宗耀祖的事情,大概也和比尔盖茨自封为首席架构师有关。我的这个系列开始于这个奇怪的名称,就是不想大家被架构二字糊弄,甚至可以随着我的思路,用你喜欢的语言和工具一起来实现。
接下来开始今天的正题。一开始我们需要实现的是系统的基础部分,其中包含公用函数库,日志部分和一些基本的约束。
首先来看日志部分。日志部分之所以成为系统的基础,是因为日志可以在开发过程和生产环境中为我们的调试起到很关键的作用。在开发过程中,很多工作是在编写业务层或数据层的代码,这些代码的行为是否和预期的一致,除了用IDE中的DEBUG功能,更简便快速的方法就是输出日志;而在生产环境中,遇到一些异常情况后,往往无法进行DEBUG,这时查看日志中的异常信息更是跟踪错误的几乎唯一办法。
对于日志部分,有大名鼎鼎的Apache支持的Log4net,也有微软自己实现的企业库。不过,日志部分虽然重要,却不复杂,无论自己实现或直接使用这些开源组件,都是很好的选择。
如果选择自己实现,不妨看看我的思路,跟我一块来实现它。
首先,我们看看需要得到什么样的信息。这里是我的日志输出的结果截图:
嗯,这是黑黑的控制台应用程序,我喜欢这种显示日志的方式,想看日志的时候,就切换到这个窗口,比查看文本文件还要方便。而在生产环境下,我们可以关闭它以节约服务器资源,需要查看的时候又可以打开它。
对于输出的信息中,最重要的是Message,应用程序告诉我们,它正在做什么。然后是做这件事情的时间(Time),然后是日志级别(Level),有日志级别的原因是我们可以对信息进行分级,比如我们在生产环境下不想看到太多的debug信息来干扰我们的排查错误,可以在日志程序中输入一个简单的命令来屏蔽debug信息,只看到我们需要的Error信息。再然后,Application和Category信息也许是需要的,我们可以通过它来定位程序集。
知道了要干什么,我们可以开始动手实现日志服务器。建立一个控制台程序,用你喜欢的语言,C#,java,ruby等等都可以。
首先,我们实现一个LoggerFormatter类,这个类的作用是根据传入约定的日志格式,按照我们的喜欢的方式输出。我们把输出分离开是因为控制台输出只是我们希望输出的一种形式,以后可以根据需要再实现其他的输出,例如数据库或者windows日志。分离开这个过程,有助于降低系统的耦合性。
private void Output_Light(Action<string> writeLine,Dictionary<string,string> arg) { writeLine("------------------------"); writeLine("Time:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")); if (arg.ContainsKey("app")) writeLine("Application:" + arg["app"]); if (arg.ContainsKey("category")) writeLine("Category:" + arg["category"]); if (arg.ContainsKey("level")) writeLine("Level:" + arg["level"]); if (arg.ContainsKey("message")) writeLine("Message:" + arg["message"]); if (arg.ContainsKey("stack")) writeLine("TraceStack:" + arg["stack"]); }
简单吧。当然前面省略了将日志信息解析到Dictionary的过程。
然后我们开始实现日志的接收过程。我这里实现了两种方式接收日志,一个是通过UDP协议,另一种是通过WCF,都很简单,不过需要注意的是,不能在应用程序主线程中接收消息,因为我们的主线程还需要处理日志的命令,所以我们需要使用BackgroundWorker或者使用一个单独的线程来接收消息。如果是使用UDP协议接受,我们还需要在调用LoggerFormatter的时候,对调用加上并发锁,否则几个消息同时到达的时候可能导致输出的混乱。
lock(this.GetType()) { var output = new LoggerFormatter(msg); output.Output(Console.WriteLine); }
接收的过程:
var log = new Log(); var ips = new IPEndPoint(IPAddress.Any, TCP_PORT); _udp = new UdpClient(ips); _udpReceived = new BackgroundWorker(); _udpReceived.DoWork += (e,s) => { try { while (true) { var b = _udp.Receive(ref ips); log.Output(Encoding.UTF8.GetString(b)); } } catch(Exception ex) { log.Output("Log Server Error:"+ex.Message); } }; _udpReceived.RunWorkerAsync();
本来想把构思的几个部分都写完,一转眼就有这么大篇幅了,明天再见。刚开始的会很基础,后面会慢慢的复杂起来,先易后难嘛,希望各位能和我有同样渐入佳境的感受。