ロギング用フレームワークの作成

結局ロギング用のフレームワークは自作することに。一応社内で使われているものはあるんだけど、アレを使うくらいならスタックトレースをファイルにリダイレクトするとかの方がよっぽどましだ。発生場所は分からない、例外のクラスの名前はなんかどっかで決めたらしい意味不明なコードに変換される、ログ用のテーブルを作らなければならない、ログのレベルという概念がない、staticで固めてあるから1プログラム1ロガー・・・って、本当に何かの役に立ったことあるんだろうか?これ。


と言うことで、今回作るフレームワークはこれらの欠点がなく、ある程度汎用的に、でも出来るだけ早く欲しいからパフォーマンスとかはとりあえず気にしないことに。
参考にしたのは、log4netの設定ファイル。ただし、log4netのようにapp.config等に記述するのではなく、専用の設定ファイルを用意することに。
設定ファイルはこんな感じ。

<log4n>
  <logger name="logger-1">
    <!--
    priorityは優先度を表し、この値が小さいものほど優先される。
    優先度が高いAppenderへのAppendが成功すると、優先度が低いAppenderにはAppendされない。
    優先度が同じものがある場合、優先度が同じAppender全てのAppendされ、
    どれか1つでも失敗すると次の優先度のAppenderにAppendされる。
    指定しないとint.MaxValueが設定される。
    -->
    <appender class="Log4N.Appender.ConsoleAppender" priority="1">
      <!--
      ${xxx}は対応する文字列に置き換えられる。
      また、${space}は$s、${tab}は$t、${exception}は$e、${newline}は$nという略記法が使用可能。
      -->
      <format>
        ${space}${tab}
        file:${file},
        class:${class},
        method:${method},
        line:${line},
        exception:${exception},
        message:${message},
        level:${level},
        date:${date:yyyyMMdd},
        logger:${logger},
        ${newline}
      </format>
    </appender>
  </logger>
  <!-- DBへのロギングが失敗したら、ファイルに落とす設定の例 -->
  <logger name="logger-2">
    <appender class="Log4N.Appender.DbAppender" priority="1">
      <param name="connection-string" value="..."/>
      <format>
        INSERT INTO Logs VALUES(@msg, @date);
      </format>
      <format-param name="@msg">
        ${level}${message}
      </format-param>
      <format-param name="@date">
        ${date}
      </format-param>
    </appender>
    <appender class="Log4N.Appender.FileAppender" priority="2">
      <!-- paramのvalue属性中でも${xxx}による置き換えは可能 -->
      <param name="file" value="${date:yyyy-MM-dd}.log"/>
      <param name="isAppend" value="true"/>
      <format>${level}${message} ${date}$n</format>
    </appender>
  </logger>
  <levels>
    <!-- 出力レベルとロガーを結びつける -->
    <level value="Fatal, Error, Warn">
      <logger-name value="logger-1"/>
      <logger-name value="logger-2"/>
    </level>
    <level value="Info">
      <logger-name value="logger-1"/>
    </level>
  </levels>
</log4n>

使い方はとても簡単。

// Loggerは内部にstaticなIDictionaryを持っており、読み込んだロガー(というかAppenderのリスト)を保持している
// もしロガーがすでに読み込まれているなら、ファイルを読み込まずにIDictionaryからAppenderのリストを取得する
Logger logger = new Logger("設定ファイルのパス", "使用するロガーの名前");
logger.Info("hogehoge");

ログ用のメソッドは基本的にはlog4netを参考にしたけど、より限定的な感じに。
例えば、Fatal系とError系のメソッドは必ず例外オブジェクトが必要だったり、Info系のメソッドには例外オブジェクトを引数としてとるメソッドを作らなかったり。


今日ほとんどの実装が終わって、あとはフォーマット周りをどうするかだけ。
まずは置換するだけの実装でやってみるか。


log4netより機能面では劣る部分が多いものの、今回のプロジェクトに必要な部分は組み込んだのであとは使ってもらうだけだ。
ちなみに、使用者に見える部分は設定ファイルとLoggerのみになるようにしたことで、使うのが面倒、という言い訳ができないようにしておいた。
今まで使ってるやつが

Logger.Log(...);

という超単純なAPIなだけに、ね・・・


全部終わったらlog4netソースコード見てみるか。
それにしても、C#XMLの処理がやばいくらい簡単だということが分かった。ReadSubtreeメソッドが便利すぎる。