Trace から TraceSource へ

.NET 1.1 における標準的で低レベルなデバッグ情報は、System.Diagnostics.Debug クラスと System.Diagnostics.Trace クラス行える。*1 .NET 2.0 では、さらに System.Diagnostics.TraceSource クラスが追加され、こちらを利用することが推奨になっている。

MSDN: TraceSource

TraceSource は拡張されたトレース システムとして機能するためのもので、以前の Trace トレース クラスと Debug トレース クラスの静的メソッドの代わりに使用できます。前バージョンの Trace クラスと Debug クラスも引き続き使用できますが、トレースには TraceSource クラスを使用することをお勧めします。

TraceSource はインスタンスが必要

TraceSource クラスの多くのメソッドは Debug クラスや Trace クラスと同様に ConditionalAttribute によって TRACE とマークされておりコンパイルオプション等によってその機能の多くを停止できるようになっている。また、TRACE とマーキングされていることから、Debug クラスよりも Trace クラスに近い用途で利用されることが想像できる。
Trace クラスと TraceSource クラスの大きな違いは、TraceSource クラスはインスタンス生成を必要とするというところだろう。

 (1)
  Trace.WriteLine("msg...");

 (2)
  TraceSource source = new TraceSource("Name");
  source.TraceEvent(TraceEventType.Verbose, 1, "msg...");

(1) の記述方法だと、TRACE が未定義の環境であればコンパイルされた IL イメージからは綺麗サッパリ消滅するのだが、(2) の記述方法では、1行目の TraceSource の生成処理は TRACE が見て意義であっても IL イメージ上に残る。これはちょっとしたことを嫌うような人にとって TraceSource を敬遠する理由になってしまうのではないかと思われる。

TraceSource と SourceSwitch

さて、肝心の TraceSource の機能だが、Trace クラスが WriteLine(), WriteLineIf(), Indent() といった非常に低レベルな操作と BooleanSwitch や TraceSwitch のような低レベルなスイッチ設定*2しかサポートしていないのに対して、TraceSource クラスでは、TraceData() および TraceEvent() と、比較的抽象的な操作を提供し、対応したスイッチ SourceSwitch も TraceSource に対応した設計になっています。
TraceSwitch と SourceSwitch はトレースレベルという同じようなものを設定するようになっていますが、TraceSwitch は設定値を読んだり設定値を見てトレース処理を変化させるのはコンシューマ側で制御する項目であるのに対し、SourceSwitch はトレースの出力の可否を判断する基準としてフレームワーク化されています。

Trace.Refresh()

.NET 2.0 になって、スイッチの設定などのアプリケーション構成ファイルの System.Diagnostics セクションを書き換えた場合には、このメソッドを呼び出すことで再読み込みを実施してくれるようになっている。繰り返し可能な操作に対するトレースをアプリケーションを再起動することなく On/Off できるため結構便利なので覚えておくとよいかもしれない。

TraceListner を構成ファイルから作成・設定する

id:ladybug:20041006#p6 の終盤の Main() の実装では、TraceListener のインスタンスを生成して設定するコードが実装されているが、この部分に関しては本来はコンパイルされたアセンブリには含める必要がない処理である。この処理を1つのメソッドにまとめて ConditionalAttribute でマークすればいいのだが、.NET 2.0 からは TraceListner をアプリケーション構成ファイルから追加することができるようになっている。場所は

<System.Diagnostics>
  <Trace autoflush="false" indentsize="4">
    <Listeners>
      <add type="type" initializeData="data" name="name" />
    </Listeners>
  </Trace>
</System.Diagnostics>

type 属性でクラス名を指定することは必須であるが、initializeData と name は省略可能になっている。name 属性はリスナに名前をつけて判別するために必要になればつけるもので、initializeData はリスナに与えるパラメータとなっている。標準で提供されるリスナは、

クラス名 initializeData 機能
ConsoleTraceListener bool Console.Out(false,省略時) または Console.Error(true) へ出力する
TextWriterTraceListener ファイル名 ファイルへ出力する
XmlWriterTraceListener ファイル名 XML ファイルへ出力する
DelimitedListTraceListener ファイル名 ";"区切り*3でファイルへ出力する
EventLogTraceListener ソース名 指定したソースへイベントログとして出力する

となっている。最初の2つはベタなログを出力し、残りの3つは構造化された出力を行うようになっている。

TraceSource へ TraceListner 設定する

TraceSource も、Trace 同様にコードおよび構成ファイルから TraceListner を追加することができる。記述セクションが から へ変化するだけで基本的な記述には差異がないのだが、TraceSource 専用の属性として traceOutputOptions を追加で指定することができる。

フラグ 説明
DateTime 日付時刻を含める DateTime=2006-02-07T10:42:56.0436250Z
Timestamp タイムスタンプを含める Timestamp=105372520914645
Callstack 呼び出し履歴を含める Callstack=場所 Program.Main()*4
ProcessId プロセス ID を含める ProcessId=3136
ThreadId スレッド ID を含める ProcessId=346
LogicalOperationStack 論理演算履歴を含める LogicalOperationStack=ヘッダ読み込み, ファイル読み込み

複数指定するときはカンマで区切って指定する。

<configuration>
  <system.diagnostics>
    <sources>
      <source name="TraceTest" 
        switchName="sw" switchType="System.Diagnostics.SourceSwitch">
        <listeners>
          <add
            type="System.Diagnostics.ConsoleTraceListener"
            initializeData="true"
            traceOutputOptions="DateTime, Callstack" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="sw" value="Information"/>
    </switches>
  </system.diagnostics>
</configuration>

では、対応する TraceSource クラスの名前を name 属性で指定し、対応するスイッチの名前と型を switchName と switchType で指定する。標準の SourceSwitch クラスでは特定のトレースレベル以上のトレースを対象とすることができ、上記の設定では Information 以上のレベルのトレースが記録されることになる。
LogicalOperationStack について簡単に触れておくと、これはロジックの実装上の経路を記録するものになっている。たとえば、上記の例では「ファイル I/O 処理の中のヘッダ読み込み処理」から呼び出されたことを示す。うまく使えばスタックトレースのような低レベルな情報よりも見やすい反面、ASP.NET などの既存のフレームワークからも使用されているため多少注意が必要なものでもある。あまり良い例ではないが、

  public static Image LoadFromFile(string filename)
  {
    Trace.CorrelationManager.StartLogicalOperation("ファイル読み込み");
     :
  }

  public void SaveToFile(string filename)
  {
    Trace.CorrelationManager.StartLogicalOperation("ファイル書き込み");
     :
  }

  void ReadHeader()
  {
    Trace.CorrelationManager.StartLogicalOperation("ヘッダ読み込み");
     :
  }

  void Seek(Stream stream, long pos)
  {
    traceSource.TraceInformation("seek to " + pos);
  }

のようなかんじになっていて、読み書き両方の処理から Seek() が呼び出されている場合、CallStack オプションを使用して呼び出し履歴を舐めるよりも、LogicalOperationStack をみればファイルの読み書きどちらから Seek() が呼び出されているかひと目で判断することができる。

sharedListners

Trace にも TraceSource にも、複数の TraceSource にも、共通の TraceListener を設定するためには、 セクションを使用すると便利になっている。

<configuration>
  <system.diagnostics>
    <sources>
      <source name="TraceTest" 
        switchName="sw" switchType="System.Diagnostics.SourceSwitch">
        <listeners>
          <add name="console" />
        </listeners>
      </source>
    <sources>
      <source name="TraceTest2" 
        switchName="sw2" switchType="System.Diagnostics.SourceSwitch">
        <listeners>
          <add name="console" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="sw" value="Information"/>
      <add name="sw2" value="Error"/>
    </switches>
    <sharedListeners>
      <add name="console"
           type="System.Diagnostics.ConsoleTraceListener"
           initializeData="true"
           traceOutputOptions="DateTime, Callstack" />
    </sharedListeners>
  </system.diagnostics>
</configuration>

に console という名前のリスナを設定し、TraceTest と TraceTest2 では名前を指定してこのリスナを設定している。

*1:参項 id:ladybug:20041006#p2

*2:参項 id:ladybug:20041006#p4

*3:Delimiter プロパティで変更可能

*4:これは Main() から直接呼び出しているためで、例外などの Stacktrace プロパティのように複数行で記録されます