• LogAnalysiser软件诞生始末


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

    好了,这个软件的介绍就到了这里,如果觉得有帮助,还请帮助顶一下,谢谢。

    源码下载

    点击这里下载源码

  • 相关阅读:
    tomcat控制台运行窗口中文乱码
    jquery html5 file 上传图片显示图片
    修改输入框placeholder文字默认颜色-webkit-input-placeholder
    H5移动端知识点总结
    PostgreSQL
    PostgreSQL
    Hexo系列(2)
    SQL
    Java笔记-序列化的注意点
    js、css外部文件的相对路径问题
  • 原文地址:https://www.cnblogs.com/scy251147/p/2566438.html
Copyright © 2020-2023  润新知