WebアプリにおけるLog4jのログファイル出力先の指定方法

かなり前に悩んでたこと。

単なる相対パスだとuser.dirが基点になるため、Tomcat起動方法によって実際のファイルパスが異なってしまいます。
開発環境でEclipseから起動したらECLIIPSE_HOMEが基点に。
直接Tomcatのbin/start.batを叩いたら、TOMCAT_HOME/binが基点に。
これは気持ちが悪い。
できれば開発環境でも本番環境でもどのように実行しても同じディレクトリにログを出力して欲しい。
でも、絶対パスは書きたくないなぁと。
アプリルートディレクトリ内にあるのに、絶対パスを書くのって無駄にインストールディレクトリに依存するのでアホらしい訳です。

Googleさんに聞いてもいまいちしっくりくる方法が見つからないので、適当に考えてみました。
もしもっといい方法があれば教えてください。>どなたか

起動時にJVM引数でシステムプロパティを指定する (ボツ)

まず、Tomcatの起動スクリプトにJVM引数を指定する方法があります。
たとえば、-Dhoge.home="/home/hoge_user/webapps/hoge"とか指定して、Tomcatを起動します。
で、アプリのクラスパス上のlog4j.propertiesでは、

log4j.appender.F.file=${hoge.home}/WEB-INF/log/app.log

みたいに書くと。これはこれで機能します。
じゃ、開発環境ではどうする?Eclipseではどうする?
結局起動時設定としてフルパスのようなものを書かなきゃいけないことになる。
それじゃ直接

log4j.appender.F.file=/home/hoge_user/webapps/hoge/WEB-INF/log/app.log

って書けばいいじゃん、ということでボツ。

自力でlog4j.propertiesをロードしてLog4Jを初期化する

org.apache.log4j.PropertyConfiguratorというクラスがあります。
このconfigure(Propeties props)というメソッドを使うとlog4jを初期化することができます。
Webアプリだったら、ServletContextListener#contextInitialized()でこの初期化をすればよさそうです。

この方法の難点は2点あります。

  • (A)アプリケーションがLog4JのAPIに依存してしまう
  • (B)log4j.propertiesの置き場所

順番に片付けましょう。

(A)Log4JのAPIに依存してしまう?

commons-loggingをせっかくいれてるのに、log4jに依存する!?
直接依存するのはServletContextListener実装クラス1箇所だけということでOK、OK。
他の箇所ではcommons-loggingがロギング実装を隠蔽してくれます。
問題ありません。

(B)log4j.propertiesの置き場所

クラスパス上においておくと、起動時に勝手に読み込まれてしまいます。
今やりたいのはlog4j.propertiesを

##### to-file
log4j.appender.F=org.apache.log4j.DailyRollingFileAppender
log4j.appender.F.file=${user.dir}/WEB-INF/log/app.log
log4j.appender.F.DatePattern='.'yyyy-MM-dd
log4j.appender.F.Append=false
log4j.appender.F.layout=org.apache.log4j.PatternLayout
log4j.appender.F.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %m%n

みたいに書いておいて、ServletContextListener実装クラスで

String rootPath = event.getServletContext().getRealPath("");
System.setProperty("user.dir", rootPath);
Properties props = ...; // どこかから持ってくる!
PropertyConfigurator.configure(props);

というようにやると、ログファイルパスもうまく通るし、別の目的でuser.dirを使ってアプリルートディレクトリからのファイルパス解決したいときも簡単にできるようになる、という方法。

でも、このlog4j.propertiesをいつものようにクラスパス上においておくと、

  1. 起動時に読み込まれる
  2. その時点ではuser.dirがTomcat/binだったり、Eclipseインストールディレクトリだったり
  3. 結果、そんなディレクトリ無いよエラー

となってしまうわけです。
まあ、その後でServletContextListenerが実行されて正しく再初期化されるんですが、毎回毎回起動するたびに必ずエラーが発生するっていやじゃないですか。
というわけで、クラスパスに置くのをやめてWEB-INF/conf配下においておけばOK.

それでも起動時に

log4j:WARN No appenders could be found for logger (org.apache.catalina.startup.TldConfig).
log4j:WARN Please initialize the log4j system properly.

とか言ってくるんですが、これはまあ無視しても大丈夫かと。これ標準エラー出力かなんかで直接コンソールにでてそうなメッセージなんで。
これを消そうと思うと、適当なappenderを定義したダミーのlog4j.propertiesをクラスパスに通しておけばいいんですけど、そこまでする意味があるのか?ってことでとりあえずやらない方向で。


以上の方法が、今のところの自分の中でのベターな解です。

いかがでしょ?