萬惡的加班還在延續(xù)著,分析軟件日志分析的頭疼還是沒有能夠找到問題的癥結(jié)所在。 五十多兆的日志文件中,很多都是沒用的,有用的信息都被這些無用的信息給推攘到了不知名的角落里。我愣是找了一個小時,找到的有用的信息寥寥無幾,抬頭望望遠(yuǎn)處,已經(jīng)感覺到有些眼暈了。 考慮到每天都要進(jìn)行這樣的診斷工作,于是決定寫一個日志分析的小軟件,要求能夠過濾掉帶有指定關(guān)鍵字的行,并且能夠高亮某些關(guān)鍵字。于是,LogAnalysiser這個小工具誕生了。
程序運行效果截圖:
啟動界面(采用了Slash窗體,在程序啟動時會自動檢測缺失的配置文件或者程序集):
然后啟動到主界面(主界面包含了配置窗體,著色窗體,更新窗體):
這個是配置窗體,多個關(guān)鍵字或者子句,利用豎線分隔開,程序會自動過濾掉含有這些關(guān)鍵字的文本行:
下面的這個是著色窗體,輸入關(guān)鍵字,以數(shù)顯隔開,點擊確定按鈕可以實時實現(xiàn)關(guān)鍵字高亮:
下面這個是更新窗體,主要負(fù)責(zé)軟件更新工作:
然后這里是幫助文檔:
這就是這個軟件的大概,雖然很小,但是算是比較的全面。
下面來說下在制作過程中使用到的技術(shù):
技術(shù)一: 異步操作(采用APM模式)
關(guān)于這個模式的具體講解,可以參見我之前的博客文章:我所知道的.net異步
在軟件Slash窗體加載,關(guān)鍵字過濾以及軟件更新的時候,由于這三個操作比較耗時,所以采用了異步方式來進(jìn)行,即使用BeginInvoek和與之配對的EndInvoke方式來達(dá)到目的。 比如說軟件中的LoadAppendingText()函數(shù)主要是用來循環(huán)過濾關(guān)鍵字來達(dá)到簡化日志的目的,一旦日志文件體積非常大的情況下,這個函數(shù)將會阻塞主界面,導(dǎo)致假死狀況。針對這種情況,我利用異步方式來處理,也就是利用下面代碼進(jìn)行了封裝,從而產(chǎn)生異步效果:
#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>
/// 異步結(jié)束
/// </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
這樣,當(dāng)軟件運行的時候,界面不會卡死,一切都很流暢:
所以,從上面的異步方式看來,這種模式下,我們只需要對耗時函數(shù)利用BeginInvoke和EndInvoke進(jìn)行一下簡單的封裝即可,省時也省力。
需要說明的是,利用異步和界面交互,不得不遇到一個跨線程的問題,不過我們可以通過Form控件的Invoke方式來進(jìn)行,也就是類似如下的操作:
lblStatus.Invoke(new Action(delegate { lblStatus.Text = "更新完畢。"; }));
技術(shù)二: 委托事件傳值。
關(guān)于委托的更多詳細(xì)情況,請參見我之前的博客:淺談C#中常見的委托
在制作本軟件的過程中,著色的字體需要實時的顯示;Slash窗體檢測完畢,也需要傳值給主窗體,然后自己關(guān)閉掉。 這兩個地方都使用了委托事件來進(jìn)行,具體怎么用呢,請看下面的步驟:
首先,聲明全局委托:
/// <summary> /// 全局委托,用于著色 /// </summary> /// <param name="text">待著色文本</param> public delegate void ColorDaemonDelegate(string text);
然后再DaemonFrm窗體中(也就是進(jìn)行著色配置的窗體中),聲明一個OnColorDaemonEventHandler事件,用于拋出通知:
public event ColorDaemonDelegate OnColorDaemonEventHandler;
那么,這個通知如何拋出呢?
當(dāng)然是在點擊著色按鈕的時候拋出去,它向外界宣布:我現(xiàn)在要著色啦,于是它在以下的代碼中將著色事件拋了出去:
private void btnColor_Click(object sender, EventArgs e) { string text = txtWordDaemon.Text; OnColorDaemonEventHandler(text); //拋出事件 }
可以看出,這個事件拋出的時候,帶有一個參數(shù),這個參數(shù)就是需要高亮的關(guān)鍵字。 那么事件拋出來了,拋給誰了?誰接收到了呢? 之后的內(nèi)容估計就是我們非常常見的了,即事件注冊:
/// <summary>
/// 點擊主窗體中的著色按鈕,可以對當(dāng)前文檔進(jìn)行關(guān)鍵字高亮
/// </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();
}
利用上面的+=號,就把剛才拋出的事件給接住了,并且這個拋出的事件被主窗體給接住了。
下面是針對這個拋出的事件進(jìn)行處理:
/// <summary>
/// 著色委托事件,可以實時高亮關(guān)鍵字
/// </summary>
/// <param name="text"></param>
private void daemonFrm_OnColorDaemonEventHandler(string text)
{
RichTextBoxEx.SetColorBox(richTextBox1,richTextBox1.Text, text, Color.Red);
}
上面的RichTextBoxEx.SetColorBox是一個利用擴(kuò)展方法實現(xiàn)的函數(shù),就可以實現(xiàn)關(guān)鍵字的實時高亮,看看效果:
這樣就可以非常方便的分析日志了。
技術(shù)三:更新組件的編寫。
更新組件是軟件最常用的組件之一,本軟件的更新組件主要采用HttpWebRequest和HttpWebResponse進(jìn)行數(shù)據(jù)獲取并結(jié)合異步機(jī)制完成。
首先,來看看下載數(shù)據(jù)的函數(shù):
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; //獲取文件字節(jié)數(shù)
progress.Invoke(new Action(delegate
{
progress.Maximum = (int)totalBytes;
}));
Stream responseStream = response.GetResponseStream(); //保存到內(nèi)存
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個字節(jié)
while (paragraphByteSize > 0)
{
totalDownloadBytes += paragraphByteSize; //當(dāng)前已經(jīng)讀取的字節(jié)數(shù)
fileStream.Write(bytes, 0, paragraphByteSize); //寫入到文件
progress.Invoke(new Action(delegate
{
progress.Value = (int)totalDownloadBytes;
}));
paragraphByteSize = responseStream.Read(bytes, 0, (int)bytes.Length); //繼續(xù)讀取下一段
percent = (int)((float)totalDownloadBytes / (float)totalBytes * 100); //進(jìn)度百分比
label.Invoke(new Action(delegate
{
label.Text = "當(dāng)前已經(jīng)更新:" + percent.ToString() + "%";
}));
}
responseStream.Close();
fileStream.Close();
}
這里我已經(jīng)做了不少的注釋了,其主體的邏輯就是得到請求數(shù)據(jù),然后1字節(jié)1字節(jié)的寫入,直到下載完畢為止。
如果直接運行這個函數(shù)進(jìn)行更新的話,會造成界面假死,所以在這里我采用了和之前一樣的異步處理方式,即利用BeginInvoke和EndInvoke方式來進(jìn)行。這樣就保證了界面的流暢性。
/// <summary>
/// 開始進(jìn)行異步更新
/// </summary>
/// <param name="url">軟件地址</param>
/// <param name="filename">軟件名稱</param>
/// <param name="progress">PrograssBar進(jìn)度條</param>
/// <param name="label">Label狀態(tài)標(biāo)簽</param>
private void BeginDownload(string url,string filename,ProgressBar progress,Label label)
{
//利用Action委托進(jìn)行代理
Action<string, string, ProgressBar, Label> action = new Action<string, string, ProgressBar, Label>(DownLoadVersion);
//開始進(jìn)行異步
action.BeginInvoke(url, filename, progress, label, new AsyncCallback(EndDownload), action);
}
/// <summary>
/// 異步更新結(jié)束
/// </summary>
/// <param name="iar">異步狀態(tài)</param>
private void EndDownload(IAsyncResult iar)
{
//還原對象
Action<string, string, ProgressBar, Label> action = (Action<string, string, ProgressBar, Label>)iar.AsyncState;
//得到異步結(jié)果
action.EndInvoke(iar);
//更新異步操作狀態(tài)
lblStatus.Invoke(new Action(delegate
{
lblStatus.Text = "更新完畢。";
}));
//暫停
System.Threading.Thread.Sleep(1000);
string fileName = Application.StartupPath + "\\LogAnalysiser.exe";
//異步更新結(jié)束,啟動主程序
Process.Start(fileName);
//退出異步更新程序
Application.Exit();
}
其次,需要說明的是,既然我們是更新軟件,那么肯定需要一個網(wǎng)絡(luò)地址存儲更高版本的文件,這里我專門創(chuàng)建了一個WebService用來處理更新程序所發(fā)出的請求。
在這個WebService中,我在web.cong文件中的configurations節(jié)點下新添加了一個子節(jié)點(這個涉及到在Web.config中進(jìn)行自定義節(jié)點的設(shè)置方面的知識,可以參見我的文章:Asp.net配置文件中自定義節(jié)點詳解):
<section name="MySection" type="UpgradeServer.MySection,UpgradeServer"/>
然后在CONFIGSECTIONS節(jié)點外面加入如下配置的節(jié)點:
<MySection> <add version ="1.2.0.0" fileName="http://localhost:2187/DownLoadVersion/LogAnalysiser.exe"></add> </MySection>
其中 version代表版本號,fileName代表待更新的文件的網(wǎng)絡(luò)地址。
這樣配置完成之后,在代碼中,我們就可以使用兩個函數(shù)暴露出待更新的軟件的版本號和更新地址:
[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;
}
那么當(dāng)程序檢測到目前版本號和WEBSERVER暴露出來的版本號一樣的時候,表明服務(wù)器上面沒有最新版本,當(dāng)二者不一致的時候,則證明服務(wù)器上面有最新的版本,于是啟動更新組件,進(jìn)行更新。
代碼如下:
public bool CheckVersionAndUpgrade()
{
try
{
if (client == null)
{
client = new UpgradeFormApplication.UpgradeWebService.Service1SoapClient();
}
string upgradeVersion = client.GetUpgradeVersion(); //獲取版本號
string upgradeFileName = client.GetUpgradeFileName(); //獲取更新文件的網(wǎng)絡(luò)路徑
string currentVersion = CommonUntil.GetApplicationVersionFromExeFile(); //獲取當(dāng)前主程序的版本號
if (String.IsNullOrEmpty(upgradeVersion))
{
lblStatus.Invoke(new Action(delegate
{
lblStatus.Text = "當(dāng)前沒有最新版本。";
}));
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 = "當(dāng)前沒有最新版本。";
}));
btnUpgrade.Invoke(new Action(delegate
{
btnUpgrade.Enabled = false;
}));
return false;
}
lblStatus.Invoke(new Action(delegate
{
lblStatus.Text = "當(dāng)前存在最新版本" + upgradeVersion + ",點擊更新。。。";
}));
return true;
}
catch
{
lblStatus.Invoke(new Action(delegate{lblStatus.Text = "不能連接遠(yuǎn)程主機(jī)獲取更新,請檢查網(wǎng)絡(luò)連接!";}));
btnUpgrade.Invoke(new Action(delegate { btnUpgrade.Enabled = false; }));
return false;
}
}
那么一旦我們有新的版本需要更新的時候,我們只需要在把這個新的版本放到IIS的形如 http://*******/DownLoadVersion/的路徑下,并且修改web.config文件中的version的值為新版本號即可。當(dāng)軟件更新組件運行的時候,一旦發(fā)現(xiàn)version值改變,就會立即啟動更新程序進(jìn)行更新。
技術(shù)之四:幫助文檔自動生成。
其實這個并不能稱為技術(shù),應(yīng)為我們應(yīng)用的是自動生成軟件,但是這個幫助文檔也確實是必不可少的,它可以讓開發(fā)人員對軟件的功能一目了然。 說到自動文檔生成,這里我推薦使用.NET文檔生成工具ADB,作者博客為:HTTP://WWW.CNBLOGS.COM/LUCC/ARCHIVE/2008/09/01/1281085.HTML
這個軟件支持多種注釋的智能識別模式,并且支持多程序集合并功能。在使用本軟件之前,強(qiáng)烈建議為程序集生成XML文檔,具體做法是在項目上右擊,選擇“生成標(biāo)簽”,然后勾選上”XML文檔文件”選項。
當(dāng)我用ADB加載我的LOGANALYSISER.EXE文件的時候,我們可以看到軟件界面列出了如下的各種公共方法,公共屬性等等。 當(dāng)我們最后點擊創(chuàng)建文檔按鈕的時候,就得到了一個看上去非常專業(yè)的幫助文檔:
好了,這個軟件的介紹就到了這里.
點擊這里下載源碼