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文件的時候,我們可以看到軟件界面列出了如下的各種公共方法,公共屬性等等。   當我們最后點擊創建文檔按鈕的時候,就得到了一個看上去非常專業的幫助文檔:  

好了,這個軟件的介紹就到了這里,如果覺得有幫助,還請幫助頂一下,謝謝。

 

源碼下載

點擊這里下載源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM