万恶的加班还在延续着,分析软件日志分析的头疼还是没有能够找到问题的症结所在。 五十多兆的日志文件中,很多都是没用的,有用的信息都被这些无用的信息给推攘到了不知名的角落里。我愣是找了一个小时,找到的有用的信息寥寥无几,抬头望望远处,已经感觉到有些眼晕了。 考虑到每天都要进行这样的诊断工作,于是决定写一个日志分析的小软件,要求能够过滤掉带有指定关键字的行,并且能够高亮某些关键字。于是,LogAnalysiser这个小工具诞生了。
程序运行效果图
下面是具体的截图:
启动界面(采用了Slash窗体,在程序启动时会自动检测缺失的配置文件或者程序集):
然后启动到主界面(主界面包含了配置窗体,着色窗体,更新窗体):
这个是配置窗体,多个关键字或者子句,利用竖线分隔开,程序会自动过滤掉含有这些关键字的文本行:
下面的这个是着色窗体,输入关键字,以数显隔开,点击确定按钮可以实时实现关键字高亮:
下面这个是更新窗体,主要负责软件更新工作:
然后这里是帮助文档:
这就是这个软件的大概,虽然很小,但是算是比较的全面。
下面来说下在制作过程中使用到的技术:
技术一: 异步操作(采用APM模式)
关于这个模式的具体讲解,可以参见我之前的博客文章:我所知道的.net异步
在软件Slash窗体加载,关键字过滤以及软件更新的时候,由于这三个操作比较耗时,所以采用了异步方式来进行,即使用BeginInvoek和与之配对的EndInvoke方式来达到目的。 比如说软件中的LoadAppendingText()函数主要是用来循环过滤关键字来达到简化日志的目的,一旦日志文件体积非常大的情况下,这个函数将会阻塞主界面,导致假死状况。针对这种情况,我利用异步方式来处理,也就是利用下面代码进行了封装,从而产生异步效果:
#region Begin and End Invoke of Async mode /// <summary> /// 异步开始 /// </summary> private void BeginInvokeAppending() { Action action = new Action(LoadAppendingText); IAsyncResult result = action.BeginInvoke(new AsyncCallback(EndInvokeAppending),action); pPrograss.Maximum = GetTotalCounts(); tTick.Enabled = true; } /// <summary> /// 异步结束 /// </summary> /// <param name="iar"></param> private void EndInvokeAppending(IAsyncResult iar) { btnAnalysis.Invoke(new Action(delegate { btnAnalysis.Enabled = false; })); tTick.Enabled = false; notificationIcon.Image = (Image)WinRes.Complete; Action action = (Action)iar.AsyncState; action.EndInvoke(iar); } #endregion
这样,当软件运行的时候,界面不会卡死,一切都很流畅:
所以,从上面的异步方式看来,这种模式下,我们只需要对耗时函数利用BeginInvoke和EndInvoke进行一下简单的封装即可,省时也省力。
需要说明的是,利用异步和界面交互,不得不遇到一个跨线程的问题,不过我们可以通过Form控件的Invoke方式来进行,也就是类似如下的操作:
lblStatus.Invoke(new Action(delegate { lblStatus.Text = "更新完毕。"; }));
技术二: 委托事件传值。
关于委托的更多详细情况,请参见我之前的博客:浅谈C#中常见的委托
在制作本软件的过程中,着色的字体需要实时的显示;Slash窗体检测完毕,也需要传值给主窗体,然后自己关闭掉。 这两个地方都使用了委托事件来进行,具体怎么用呢,请看下面的步骤:
首先,声明全局委托:
/// <summary> /// 全局委托,用于着色 /// </summary> /// <param name="text">待着色文本</param> public delegate void ColorDaemonDelegate(string text);
然后再DaemonFrm窗体中(也就是进行着色配置的窗体中),声明一个OnColorDaemonEventHandler事件,用于抛出通知:
public event ColorDaemonDelegate OnColorDaemonEventHandler;
那么,这个通知如何抛出呢?
当然是在点击着色按钮的时候抛出去,它向外界宣布:我现在要着色啦,于是它在以下的代码中将着色事件抛了出去:
private void btnColor_Click(object sender, EventArgs e) { string text = txtWordDaemon.Text; OnColorDaemonEventHandler(text); //抛出事件 }
可以看出,这个事件抛出的时候,带有一个参数,这个参数就是需要高亮的关键字。 那么事件抛出来了,抛给谁了?谁接收到了呢? 之后的内容估计就是我们非常常见的了,即事件注册:
/// <summary> /// 点击主窗体中的着色按钮,可以对当前文档进行关键字高亮 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void tsBtnColor_Click(object sender, EventArgs e) { if (daemonFrm == null || daemonFrm.IsDisposed == true) { daemonFrm = new DaemonFrm(); } daemonFrm.OnColorDaemonEventHandler += new ColorDaemonDelegate(daemonFrm_OnColorDaemonEventHandler); daemonFrm.Show(); }
利用上面的+=号,就把刚才抛出的事件给接住了,并且这个抛出的事件被主窗体给接住了。
下面是针对这个抛出的事件进行处理:
/// <summary> /// 着色委托事件,可以实时高亮关键字 /// </summary> /// <param name="text"></param> private void daemonFrm_OnColorDaemonEventHandler(string text) { RichTextBoxEx.SetColorBox(richTextBox1,richTextBox1.Text, text, Color.Red); }
上面的RichTextBoxEx.SetColorBox是一个利用扩展方法实现的函数,就可以实现关键字的实时高亮,看看效果:
这样就可以非常方便的分析日志了。
技术三:更新组件的编写。
更新组件是软件最常用的组件之一,本软件的更新组件主要采用HttpWebRequest和HttpWebResponse进行数据获取并结合异步机制完成。
首先,来看看下载数据的函数:
private void DownLoadVersion(string url,string filename,ProgressBar progress,Label label) { int percent = 0; HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); request.ContentType = @"application/octet-stream"; request.Credentials = CredentialCache.DefaultCredentials; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); long totalBytes = response.ContentLength; //获取文件字节数 progress.Invoke(new Action(delegate { progress.Maximum = (int)totalBytes; })); Stream responseStream = response.GetResponseStream(); //保存到内存 Stream fileStream = new FileStream(filename, FileMode.Create); long totalDownloadBytes = 0; byte[] bytes = new byte[1024]; int paragraphByteSize = responseStream.Read(bytes, 0, (int)bytes.Length); //一次性读取1024个字节 while (paragraphByteSize > 0) { totalDownloadBytes += paragraphByteSize; //当前已经读取的字节数 fileStream.Write(bytes, 0, paragraphByteSize); //写入到文件 progress.Invoke(new Action(delegate { progress.Value = (int)totalDownloadBytes; })); paragraphByteSize = responseStream.Read(bytes, 0, (int)bytes.Length); //继续读取下一段 percent = (int)((float)totalDownloadBytes / (float)totalBytes * 100); //进度百分比 label.Invoke(new Action(delegate { label.Text = "当前已经更新:" + percent.ToString() + "%"; })); } responseStream.Close(); fileStream.Close(); }
这里我已经做了不少的注释了,其主体的逻辑就是得到请求数据,然后1字节1字节的写入,直到下载完毕为止。
如果直接运行这个函数进行更新的话,会造成界面假死,所以在这里我采用了和之前一样的异步处理方式,即利用BeginInvoke和EndInvoke方式来进行。这样就保证了界面的流畅性。
/// <summary> /// 开始进行异步更新 /// </summary> /// <param name="url">软件地址</param> /// <param name="filename">软件名称</param> /// <param name="progress">PrograssBar进度条</param> /// <param name="label">Label状态标签</param> private void BeginDownload(string url,string filename,ProgressBar progress,Label label) { //利用Action委托进行代理 Action<string, string, ProgressBar, Label> action = new Action<string, string, ProgressBar, Label>(DownLoadVersion); //开始进行异步 action.BeginInvoke(url, filename, progress, label, new AsyncCallback(EndDownload), action); } /// <summary> /// 异步更新结束 /// </summary> /// <param name="iar">异步状态</param> private void EndDownload(IAsyncResult iar) { //还原对象 Action<string, string, ProgressBar, Label> action = (Action<string, string, ProgressBar, Label>)iar.AsyncState; //得到异步结果 action.EndInvoke(iar); //更新异步操作状态 lblStatus.Invoke(new Action(delegate { lblStatus.Text = "更新完毕。"; })); //暂停 System.Threading.Thread.Sleep(1000); string fileName = Application.StartupPath + "\\LogAnalysiser.exe"; //异步更新结束,启动主程序 Process.Start(fileName); //退出异步更新程序 Application.Exit(); }
其次,需要说明的是,既然我们是更新软件,那么肯定需要一个网络地址存储更高版本的文件,这里我专门创建了一个WebService用来处理更新程序所发出的请求。
在这个WebService中,我在web.cong文件中的configurations节点下新添加了一个子节点(这个涉及到在Web.config中进行自定义节点的设置方面的知识,可以参见我的文章:Asp.net配置文件中自定义节点详解):
<section name="MySection" type="UpgradeServer.MySection,UpgradeServer"/>
然后在CONFIGSECTIONS节点外面加入如下配置的节点:
<MySection> <add version ="1.2.0.0" fileName="http://localhost:2187/DownLoadVersion/LogAnalysiser.exe"></add> </MySection>
其中 version代表版本号,fileName代表待更新的文件的网络地址。
这样配置完成之后,在代码中,我们就可以使用两个函数暴露出待更新的软件的版本号和更新地址:
[WebMethod] public string GetUpgradeVersion() { MySection section = (MySection)ConfigurationManager.GetSection("MySection"); MySectionItem item = section.Item; return item.Version; } [WebMethod] public string GetUpgradeFileName() { MySection section = (MySection)ConfigurationManager.GetSection("MySection"); MySectionItem item = section.Item; return item.FileName; }
那么当程序检测到目前版本号和WEBSERVER暴露出来的版本号一样的时候,表明服务器上面没有最新版本,当二者不一致的时候,则证明服务器上面有最新的版本,于是启动更新组件,进行更新。
代码如下:
public bool CheckVersionAndUpgrade() { try { if (client == null) { client = new UpgradeFormApplication.UpgradeWebService.Service1SoapClient(); } string upgradeVersion = client.GetUpgradeVersion(); //获取版本号 string upgradeFileName = client.GetUpgradeFileName(); //获取更新文件的网络路径 string currentVersion = CommonUntil.GetApplicationVersionFromExeFile(); //获取当前主程序的版本号 if (String.IsNullOrEmpty(upgradeVersion)) { lblStatus.Invoke(new Action(delegate { lblStatus.Text = "当前没有最新版本。"; })); btnUpgrade.Invoke(new Action(delegate { btnUpgrade.Enabled = false; })); return false; } if (String.IsNullOrEmpty(currentVersion)) { return false; } if (currentVersion.Equals(upgradeVersion)) //如果没有更高的版本号 { lblStatus.Invoke(new Action(delegate { lblStatus.Text = "当前没有最新版本。"; })); btnUpgrade.Invoke(new Action(delegate { btnUpgrade.Enabled = false; })); return false; } lblStatus.Invoke(new Action(delegate { lblStatus.Text = "当前存在最新版本" + upgradeVersion + ",点击更新。。。"; })); return true; } catch { lblStatus.Invoke(new Action(delegate{lblStatus.Text = "不能连接远程主机获取更新,请检查网络连接!";})); btnUpgrade.Invoke(new Action(delegate { btnUpgrade.Enabled = false; })); return false; } }
那么一旦我们有新的版本需要更新的时候,我们只需要在把这个新的版本放到IIS的形如 http://*******/DownLoadVersion/的路径下,并且修改web.config文件中的version的值为新版本号即可。当软件更新组件运行的时候,一旦发现version值改变,就会立即启动更新程序进行更新。
技术之四:帮助文档自动生成。
其实这个并不能称为技术,应为我们应用的是自动生成软件,但是这个帮助文档也确实是必不可少的,它可以让开发人员对软件的功能一目了然。 说到自动文档生成,这里我推荐使用.NET文档生成工具ADB,作者博客为:HTTP://WWW.CNBLOGS.COM/LUCC/ARCHIVE/2008/09/01/1281085.HTML
这个软件支持多种注释的智能识别模式,并且支持多程序集合并功能。在使用本软件之前,强烈建议为程序集生成XML文档,具体做法是在项目上右击,选择“生成标签”,然后勾选上”XML文档文件”选项。
当我用ADB加载我的LOGANALYSISER.EXE文件的时候,我们可以看到软件界面列出了如下的各种公共方法,公共属性等等。 当我们最后点击创建文档按钮的时候,就得到了一个看上去非常专业的帮助文档:
好了,这个软件的介绍就到了这里,如果觉得有帮助,还请帮助顶一下,谢谢。
源码下载