先日・・・Webアプリケーション停止時にログが出力されないという事象に遭遇しました
その原因と解決方法をメモっておこうかと思います。
アプリケーションの構成要素
本事象が発生したアプリケーションの構成要素(ざっくりな構成要素)は以下の通りです。
- Tomcat (warデプロイ)
- Spring Framework(Spring MVC) ※諸事情により「Spring Boot」は使ってない・・・
- Logback
なぜログが出力されなかったのか?
Webアプリケーション停止時に行う処理(破棄処理)にて、「Logbackのログ出力機能を停止する処理」が一番最初に呼び出されていることが原因でした。
ざっくりいうと以下のような流れで処理が実行されていました。
- サーブレットコンテナの停止開始
- Logbackのログ出力機能の停止
- Springのアプリケーションコンテキストの破棄処理 → ここで出力しているログが出ない・・・
- サーブレットコンテナの停止完了
誰がLogbackのログ出力機能を停止しているのか?
Logback(logback-classic)には、LogbackServletContainerInitializer
というクラスが含まれており、Servlet 3.0以上をサポートするアプリケーションサーバにデプロイすると、LogbackServletContextListener
クラスがサーブレットコンテナに自動的に登録される仕組みになっていました。
今回のケースでは、LogbackServletContextListener
のcontextDestroyed
メソッドの中で「Logbackのログ出力機能を停止する処理」が実装されていました。
ログを出力するにはどうすればよい?
今回のケースでは・・・
- サーブレットコンテナの停止開始
- Springのアプリケーションコンテキストの破棄処理
- Logbackのログ出力機能の停止
- サーブレットコンテナの停止完了
といった感じで、「Logbackのログ出力機能を停止する処理」が一番最後に実行されるようにすればよさそうです。
LogbackServletContextListener
の自動登録の無効化
処理順番を変更するためには・・・まず LogbackServletContextListener
の自動登録機能を無効化します。無効化する方法はいくつかありますが、本エントリーでは、サーブレットコンテナのパラメータを使って無効化する方法を紹介します。
web.xml
<context-param>
<param-name>logbackDisableServletContainerInitializer</param-name>
<param-value>true</param-value>
</context-param>
NOTE:
システムプロパティまたは環境変数を利用して無効化することも可能です。
手動で LogbackServletContextListener
を追加
自動登録を無効化した上で、Springのアプリケーションコンテキストの初期化・破棄を行うためのリスナークラス(ContextLoaderListener
)より前に、LogbackServletContextListener
クラスを定義します。
<listener>
<listener-class>ch.qos.logback.classic.servlet.LogbackServletContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
こうすることで、(とりあえず・・・Tomcatでは)「Springのアプリケーションコンテキストの破棄処理」が行われた後に、「Logbackのログ出力機能を停止する処理」が実行されるようになりました。