きな粉もち.net

.NET関連仕事に携わっています。OSSのソースを読んで気がついたことを中心に呟いたりブログに投稿したりしています。最近はUiPathを使ったRPAも研究中。気軽にフォローやツッコミよろしくおねがいします! Gitはここを使っています https://github.com/kinakomotitti

log4net × FileSystemWatcher × 設定ファイルの監視

引き続き、log4netの起動処理の解読をする中で学んだ技術についてまとめていきます。
今回は、System.IO.FileSystemWatcherクラスについてです。

log4netでは、FileSystemWatcherを使って設定ファイルの変更を監視し、変更があった場合設定ファイルを再読み込みするように動作させることができます。

設定方法

この「設定ファイルの変更を監視し、変更検出時に再読み込み」設定を有効/無効にするためには、log4netの呪文の値を変更します。
↓呪文↓

assembly: log4net.Config.XmlConfigurator(Watch = true,ConfigFile =@".\config\log4net.config")]

呪文の設定方法とか読み込みの処理は以下の記事でちょっとまとめてます。
気が向いた時に思い出してくださいw
kinakomotitti.hatenablog.com

呪文のWatchに設定する値をTrueにすることで、この設定が有効になります。

実装

設定ファイルの変更を監視して、変更があったときにファイルを再読み込みする・・・・
さて、どうやって実現しているかをlog4netのコードから確認してみます。

実装は以下のようになっています。(log4net.Core.XmlConfiguratorクラスから抜粋)

public ConfigureAndWatchHandler(ILoggerRepository repository, FileInfo configFile)
{
    	m_repository = repository;
    	m_configFile = configFile;

    	// Create a new FileSystemWatcher and set its properties.
ここ1→	m_watcher = new FileSystemWatcher();
    
    	m_watcher.Path = m_configFile.DirectoryName;
    	m_watcher.Filter = m_configFile.Name;

    	// Set the notification filters
    	m_watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName;

    	// Add event handlers. OnChanged will do for all event handlers that fire a FileSystemEventArgs
ここ2→  m_watcher.Changed += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged);
    	m_watcher.Created += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged);
    	m_watcher.Deleted += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged);
    	m_watcher.Renamed += new RenamedEventHandler(ConfigureAndWatchHandler_OnRenamed);

    	// Begin watching.
ここ3→	m_watcher.EnableRaisingEvents = true;

    	// Create the timer that will be used to deliver events. Set as disabled
ここ4→	m_timer = new Timer(new TimerCallback(OnWatchedFileChange), null, Timeout.Infinite, Timeout.Infinite);
}

ここ1:
本日の主役のFileSystemWatcherクラスのインスタンスを生成しています。

ここ2:
ファイルが、変更された、作成された、消された、時に、ConfigureAndWatchHandler_OnChangedメソッドが、
リネームされたときに、ConfigureAndWatchHandler_OnRenamedメソッドが実行されるようにイベントを登録しています。
※ConfigureAndWatchHandler_OnChanged、ConfigureAndWatchHandler_OnRenamedでは、メソッドは別で定義されていますが、処理は同じです。
 上の2つのメソッドでは、”ここ4”で生成しているTimerインスタンスを使って、コールバック関数のOnWatchedFileChangeメソッドを実行させています。
 なお、ファイルが変更された通知が来てから500ミリ秒後に1回だけ実行されるように実装されています。

//TimeoutMillis = 500
m_timer.Change(TimeoutMillis, Timeout.Infinite);

ここ3:
FileSystemWatcherでは、インスタンスをNewするだけではなく、明示的に監視をOnしてあげないといけません。
EnableRaisingEventsプロパティをTrueにすることで、監視を開始することができます。(忘れがち?w)

ここ4:
Timerクラスのインスタンスを作成。
第一引数:A System.Threading.TimerCallback を実行するメソッド。
      → 実行したい処理的な感じ
第二引数:コールバック メソッドで使用される情報を格納したオブジェクトまたは null。
      → 第一引数の引数的な感じ
第三引数:System.Threading.Timeout.Infiniteはタイマーが起動しないようにするために設定する
     0は、タイマーを即時起動させるときに設定する
     それ以外は、指定された時間が経過したのちにタイマーが起動する
      → 遅延時間の設定
第四引数:呼び出しの間の時間間隔。
      → スヌーズ機能的な感じ

FileSystemWatcherについて気を付けること

FileSystemWatcherには、InternalBufferSizeというプロパティがあります。
このInternalBufferSizeは4KB ~ 64KBの範囲で設定できるFileSystemWatcherインスタンス内のメモリバッファです。
4KB以下に値を設定しようとすると、その設定値は破棄され、4KBに設定されるそうです。
このファイルバッファは、システム→コンポーネントAPIとファイルの通知を伝えるときに利用されます。
そのため、一度に大量の変更が生じてこのバッファがいっぱいになってしまった場合は、ファイルの変更情報が失われてしまうことがあります。 ←これこわいですねwバグになりそう。。。
バッファの節約のためにも、
・NotifyFilterプロパティ
・IncludeSubdirectoriesプロパティ
上記2つの設定を適切に行い、不要な変更通知をフィルタ処理する必要があります。
log4netでは、バッファは規定値を使っていますが、NotifyFilterプロパティを変更していました。
FileSystemWatcher.InternalBufferSize プロパティ (System.IO)

まとめ

log4netでWatch=trueを設定すると、FileSystemWatcherが起動され、log4net.configファイルが監視されるよ!
メモリ節約する場合や、設定が頻繁に変わらないから・・・って場合は、Falseにしたほうがよさそうですね。
知らず知らずのうちに、Trueにしちゃってた気がする・・・

なお、FileSystemWatcherには、ネットワークドライブ経由の条件下でバグるらしいので、利用するときは気をつけてください!
FileSystemWatcher doesn’t fire events for monitored network drive after changing InternalBufferSize – Kim Hamilton

もっとFileSystemWatcherクラスについて詳しく知りたい場合は、以下の記事が参考になると思います。
Detecting File Changes with FileSystemWatcher