log4netでファイル名を動的に変更してログ出力するには

2021-01-17

Appenderでファイル名を動的に指定する方法は要注意。他サイトで紹介されているAppenderを使用した方法では問題が発生したので、違う方法を探した。

絶対譲れない条件

・ファイル名をプログラム内で動的に設定したい
・ファイル名の項目以外はlog4netの設定ファイルから設定を取得したい

既存の情報では何故無理だったのか

試した方法

appenderを作成後、loggerの設定を変え出力先を変更する

問題点

appender作成の地点で空のファイルが作成されてしまう。
作成されるのが1ファイルだけであれば許容できたが、日付でローリングしていると日付の分だけファイルが作成されてしまう+日付でローリングしている場合はlog4netではログファイルが消されないのでひたすら空ファイルが作成される。
初期値で使わんから適当な値でいいやーと設定すると思わぬ場所にひたすらファイルが作成されてる…なんてことも。

設定ファイルでファイル名指定しているfile項目をそもそも作らなきゃいいんじゃ…とか色々やったけどfile項目ないとlog4netさん怒るんですよね。

下に貼ったのが実際に実行したときの図。空ファイルなのですが、サイズは1KBと表示。UTF-8のBOM付で出力してるのでBOMの分かな。

名前変更前に空ファイルができてる図(名前変更後はlogフォルダの中に作成される)

解決策

log4netの設定ファイルをxmlとして読み込んで、編集してからlog4netに設定ファイルを送り込む。
あとはできてしまった空ファイルを毎回削除するプログラムを書くことでも回避可能。

今回は要件によって削除できなかったのでlog4netの設定ファイルを動的に変更することで空ファイルが作成されるのを回避しました。

設定ファイル

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<log4net>
		<appender name="testlog" type="log4net.Appender.RollingFileAppender">
			<Encoding value="utf-8" />
			<appendToFile value="true" />
			<StaticLogFileName value="false" />
			<rollingStyle value="Date" />
			<file value="名前変更前のlog" />
			<DatePattern value='yyyyMMdd".log"' />
			<lockingModel type="log4net.Appender.FileAppender+InterProcessLock" />
			<filter type="log4net.Filter.LevelRangeFilter">
				<param name="LevelMin" value="INFO" />
				<param name="LevelMax" value="Error" />
			</filter>
			<layout type="log4net.Layout.PatternLayout">
				<ConversionPattern value="%date [%-5level]  - %message%n" />
			</layout>
		</appender>
		<root>
			<level value="ALL" />
			<appender-ref ref="testlog" />
		</root>
	</log4net>
</configuration>

本体

using System.Reflection;
using System.IO;
using System.Xml;

namespace log4netSpeedMeasure
{
    class Program
    {
        static void Main(string[] args)
        {
            // exeと同じフォルダに存在するlog4netのconfigを読み込む
            string exePath = Assembly.GetExecutingAssembly().Location;
            string exeFolderPath = Directory.GetParent(exePath).FullName;

            // log4netの設定ファイルの内容を編集する
            XmlDocument log4netConfig = new XmlDocument();
            log4netConfig.Load(Path.Combine(exeFolderPath, "log4net.xml"));
            XmlNode nodeLog4net = log4netConfig.SelectSingleNode("configuration/log4net");
            XmlNode nodeFile = nodeLog4net.SelectSingleNode("appender/file");
            nodeFile.Attributes["value"].InnerText = Path.Combine(exeFolderPath, "名前変更後のlog");

            // log4netの設定ファイルとして編集したXmlElementを格納
            // <configuration>からでなく、< log4net >から下をのノードを設定すること
            log4net.Config.XmlConfigurator.Configure((XmlElement)nodeLog4net);

            // 試しにログ出力してみる
            log4net.ILog logger = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
            logger.Info("実行されたよ");
        }
    }
}

実行の際の注意事項

log4netに自動で設定ファイルを読み込んでもらう場合、AssemblyInfoに1文([assembly: log4net.Config(以下略)])を追加しないと動かない。
ただし今回の場合、プログラム内で編集したものをlog4netに読んでもらわないといけないので1文の追加は必要ない。むしろ残っていると名前変更前の空ファイルが出力され続ける。

実行結果

実行後のフォルダの様子。
無事「名前変更前ののlog」が出力されなくなり、「名前変更後のlog」が出力されるようになった。中のログ本文も問題なく出力できた。