【奇技淫巧】讓TextWriterTraceListener依日期分檔
| | 4 | | 9,447 |
TextWriterTraceListener可將Trace機制輸出內容保存於檔案,便於存證、追蹤及偵錯,而Trace機制普遍應用於不少微軟產品或平台中,像是WCF Tracing,即可透過config設定將執行過程的追蹤資訊寫成檔案。
例如: 在WCF所在的web.config加入以下設定後
</sectionGroup>
</configSections>
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="traceListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="d:\WCFTrace.txt"/>
</listeners>
</source>
</sources>
</system.diagnostics>
<appSettings/>
可在WCFTrace.txt中找到詳細的追蹤資訊:
不過在實務應用時,會發現TextWriterTraceListener有點美中不足,最主要是TextWriterTraceListener啟用時Log檔案路徑是固定的,長久運行下來,檔案會變十分肥大(尤其追蹤資料通常很細很多),要在冗長的Log中追查特定時點發生的問題頗為困難。有個解決方式是寫個歸檔排程,在每天凌晨00:00時將現有檔案更名加上日期分檔保存,隔天起寫入原檔案,達到一天一檔的目的。但我一直在想,如果TextWriterTraceListener能自動每天獨立成一個檔案,那該有有多好?
(PS: MSDN提過一個CircularTraceListener的範例,是用兩個檔案輪流切換以控制檔案不要超過檔案上限,與理想的每日分檔有些距離)
於是動了念頭想改寫TextWriterTraceListener以實現按日分檔! 由於想用最少的Code達到目標,動用了點Hacking手法,繼承TextWriterTraceListener享受大部分的原有功能,再利用Reflection去偷改TextWriterTraceListener的私用欄位fileName,使其可以依時間變化不同的路徑及檔案名稱,就達成了每天一個檔案,甚至於每個小時獨立一個檔案的要求。
程式碼如下,有興趣的人請自行參照: (偷改父類別私用變數的Hacking行徑多為名門正派所不恥,但可以做到程式本體不用50行就KO,又能充分享受破解樂趣,對我來說實在誘人,不玩一下對不起深藏心中的駭客魂呀~~~ 衛道人士請自行迴避,不然就當成趣味惡搞一笑置之。)
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Permissions;
namespace Darkthread
{
/// <summary>
/// 會依每天日期獨立個別檔案的TextWriterTraceListener
/// </summary>
[HostProtection(SecurityAction.LinkDemand, Synchronization = true)]
public class DailyTextLogListener : TextWriterTraceListener
{
/// <summary>
/// 入Log路徑設定,可使用{0:yyyyMMdd}依日期動態變化
/// </summary>
public string LogPathPattern { get; set; }
/// <summary>
/// 建構式,傳入Log路徑設定,可使用{0:yyyyMMdd}依日期動態變化
/// </summary>
/// <param name="logPathPattern"></param>
public DailyTextLogListener(string logPathPattern)
{
LogPathPattern = logPathPattern;
}
/// <summary>
/// 由Writer取出目前使用的Log檔路徑
/// </summary>
/// <returns></returns>
private string GetCurrentFilePath()
{
if (Writer == null) return null;
return ((Writer as StreamWriter).BaseStream as FileStream).Name;
}
/// <summary>
/// 由當天日期及LogPathPattern設定決定當時應使用的Log檔路徑
/// </summary>
/// <returns></returns>
private string GetDailyFilePath()
{
//未指定LogPathPattern時給予預設值
if (string.IsNullOrEmpty(LogPathPattern))
LogPathPattern = ".\\{0:yyyyMMdd}.log";
return string.Format(LogPathPattern, DateTime.Now);
}
/// <summary>
/// 確認目前使用的Writer有指向正確的檔案
/// </summary>
private void EnsureDailyLogPath()
{
//檢查目前的路徑是否為正確的路徑
string dailyLogPath = GetDailyFilePath();
if (dailyLogPath != GetCurrentFilePath())
{
//取出目錄部分
string dirPath = Path.GetDirectoryName(dailyLogPath);
//先確保路徑存在,若不存在則建立之
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
//關閉目前使用的Log檔案
base.Close();
//# Hacking: 叔叔有練過 #
//利用Reflection偷改TextWriterTraceListener的私有Field fileName
typeof(TextWriterTraceListener)
.GetField("fileName", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(this, dailyLogPath);
//將Writer設為空值,強迫稍後改用新的檔案路徑建立FileStream
base.Writer = null;
}
}
//覆寫Write,寫入資料前確認檔案路徑
public override void Write(string message)
{
EnsureDailyLogPath();
base.Write(message);
}
//覆寫WriteLine,寫入資料前確認檔案路徑
public override void WriteLine(string message)
{
EnsureDailyLogPath();
base.WriteLine(message);
}
}
}
有一點要注意,依MSDN文件及實測,自訂的TraceListener元件,在編譯時記得要加上數位簽署且在web.config要使用強式名稱才可運作。例如:
<add name="traceListener"
type="Darkthread.DailyTextLogListener, Darkthread.DailyTextLogListener, Version=1.0.0.0, Culture=neutral, PublicKeyToken=705dce557f423df8"
initializeData="d:\MyLogs\{0:yyyyMM}\{0:MMdd}.log"/>
如此,就能在d:\MyLogs\201202\0211.log找到今天的追蹤記錄囉! 其實要一個小時分一個檔案也不是問題,將initializeData設成\{0:MMddHH}.log就可以了,很酷吧!
Comments
# by pH.minamo
其實,你怎麼不直接自己建立Writer就好了? 像這樣: base.Writer = new StreamWriter(dailyLogPath); 還有這種比較path的方式說真的滿粗糙的,大小寫反斜線相對位置都有可能出錯,起碼用Path.GetFullPath,或者自己暫存dailyLogPath
# by Jeffrey
to pH.minamo, 不自己建立StreamWriter的考量是在base.EnsureWriter()用了超過20行的程式在建立StreamWriter的過程,所以我選擇只把writer清空繼續延用原本的邏輯建立StreamWriter。 路徑比較的部分確實有出錯風險存在,謝謝您的建議~~
# by pH.minamo
我看了一下.NET BCL的EnsureWriter實作,我是覺得這樣的做法沒什麼問題,何況把自製的StreamWriter餵給TextWriterTraceListener是MSDN也認可的做法: http://msdn.microsoft.com/en-us/library/c5fw0173.aspx 如果真的想要用TextWriterTraceListener的邏輯,何不做個wrapper就好了,像這樣: https://gist.github.com/1806807 行數是多了幾行啦,不過少了Reflection我想在速度上應該是高了好幾倍 Reflection很好用,但是這個做法完全沒有意義,必須要先把decompiler打開觀察BCL的實作,然後大概省了不到十行的程式,我覺得這只是把打字的時間拿去觀察decompiled source而已。 而且事實上這個class假如搬到Mono上會直接爆掉,因為Mono的implementation沒有fileName這個private field。 我知道這個做法是趣味居多,所以我只是提供一些不使用Reflection的看法而已:) 我一直是這個blog的忠實讀者,希望您可以繼續寫和Reflection有關的文章。
# by Jeffrey
To pH.minamo, Wrapper的點子很棒,夠簡潔又符合正規解法(唯一的缺點是不夠有趣,哈!),謝謝提供這麼好的回饋~~ 歡迎您常來留言,能與各路高手交流是寫部落格最大的收獲!