万恶的加班还在延续着,分析软件日志分析的头疼还是没有能够找到问题的症结所在。 五十多兆的日志文件中,很多都是没用的,有用的信息都被这些无用的信息给推攘到了不知名的角落里。我愣是找了一个小时,找到的有用的信息寥寥无几,抬头望望远处,已经感觉到有些眼晕了。 考虑到每天都要进行这样的诊断工作,于是决定写一个日志分析的小软件,要求能够过滤掉带有指定关键字的行,并且能够高亮某些关键字。于是,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文件的时候,我们可以看到软件界面列出了如下的各种公共方法,公共属性等等。 当我们最后点击创建文档按钮的时候,就得到了一个看上去非常专业的帮助文档:
好了,这个软件的介绍就到了这里.
点击这里下载源码