zoukankan      html  css  js  c++  java
  • 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文件的时候,我们可以看到软件界面列出了如下的各种公共方法,公共属性等等。   当我们最后点击创建文档按钮的时候,就得到了一个看上去非常专业的帮助文档:  

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

    源码下载

    点击这里下载源码

  • 相关阅读:
    WCF 第四章 绑定 在多个绑定上暴露一个服务契约
    WCF 第五章 行为 事务跨操作事务流
    WCF 第五章 导出并发布元数据(服务行为)
    WCF 第五章 行为 通过配置文件暴露一个服务行为
    WCF 第五章 不支持会话的绑定的默认并发和实例
    WCF 第五章 并发和实例(服务行为)
    WCF 第五章 行为 总结
    WCF 第四章 绑定 绑定元素
    WCF 第五章 行为 事务之选择一个事务协议OleTx 或者WSAT
    WCF 第四章 绑定 比较各种绑定的性能和可扩展性
  • 原文地址:https://www.cnblogs.com/scy251147/p/2566438.html
Copyright © 2011-2022 走看看