LogAnalysiser软件诞生始末

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

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

 

源码下载

点击这里下载源码

APDUAnalysiser程序的设计最终目标是实现对高通QCAT Log分析工具的增强,对UIM Debug信息实现完整解析。使用此工具可以使开发人员更轻松、简单地从内容繁杂的Log文件中找到、提取出感兴趣的信息内容,达到快速分析、定位、解决故障的目的。 本文档分为三个部分,分别介绍APDUAnalysiser的工作原理、功能说明和安装要点。 1.工作原理 简要说明一下对Log文件导入的支持原理。 APDUAnalysiser支持两种类型Log文件的导入: 通过QXDM抓取的Log文件(*.isf)和由isf文件转化而成的文本文件(*.txt)。 程序仅实现了对文本文件的内容读取和解析,isf文件由于其格式高通未公布,其文件结构属于黑盒状态,暂时无法直接解析。 当前对isf文件的导入方案为:通过QCAT先转化为文本再导入。 当用户选取某个isf文件后,程序自动到QCAT安装目录调用qcat程序进行Log文件的文本转换,将转换结果保存到同路径同名称的文本文件中,然后将该文本通过APDUAnalysiser导入。 2.功能说明 APDUAnalysiser工具提供以下类型log的解析功能,非以下类型的信息将被工具自动过滤掉。 1).普通调试信息 APDUAnalysiser支持对普通调试信息的彩色显示,对调试信息按文件名称进行定制化过滤。 高通工具仅支持对普通调试信息按等级和功能模块过滤,这样往往容易在Log中产生大量的垃圾信息,使有用的信息不容易被发现。 APDUAnalysiser实现的对文件名称定制过滤功能有效地解决了这一问题,工具还支持定制指定的文件名和关键字集合,可以更加灵活方便地实现信息的过滤。 2).UIM Debug信息 高通工具不支持对这种类型Log的解析,开发人员只能通过经验或查阅相关规范文档来理解Log中的意思。不仅费时费力,而且不全面,容易出错。 APDUAnalysiser目标是对这类信息实现完全的自动化解析,这部分工作量非常大,目前对常用的一些信息已经有很好的解析,可以对当前的开发工作进行支持,包括如下类型的指令: a.UTK主动式命令Tag解析(支持UTK发短信的TPDU解析); b.卡文件交互命令解析; c.卡上电话本记录初始化解析; d.短信读取及TPDU解析(与QCAT工具显示方式相同,支持中文解析); e.鉴权操作Tag解析; 3).OTA信息 列表显示及按类型过滤显示。这部分Log的显示高通工具做得本身比较好,这里的功能与高通工具类似。 工具支持对字体大小和颜色的定制显示,支持对显示列表内容的快速搜索功能。在实际使用中应该能起到一定的作用。 3.安装要点 Step1:确认电脑中已经安装jre5或以上的java运行环境 Step2:在系统环境变量中添加 项:_JAVA_OPTIONS, 值:-Xms24m -Xmn1m -Xmx256m Step3.将工具包解压后,双击dist\APDUAnalysiser.jar开始使用工具 ¤请确定已安装高通工具QCAT
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值