はじめに (対象読者・この記事でわかること)
この記事は、Wildfly (旧JBoss AS) 上でJavaアプリケーションを運用しており、未処理例外のログ出力に課題を感じている開発者やシステム管理者の方を対象としています。特に、デフォルトのログ設定では情報が過剰であったり、逆に不足していると感じる方、本番環境でのログのノイズを減らしたい、あるいは特定の例外だけを詳細に記録したいといったニーズを持つ方に役立つでしょう。
この記事を読むことで、Wildflyの強力なロギングサブシステムを理解し、CLI (Command Line Interface) を使って未処理例外のログレベル、出力フォーマット、出力先などを詳細に制御できるようになります。具体的には、特定のアプリケーションやパッケージで発生した例外ログの出力深度を調整したり、特定の条件でログをフィルタリングしたりする手順を習得し、より効果的なエラー監視と運用を実現するためのヒントが得られるでしょう。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
* Javaプログラミングの基礎知識 (例外処理に関する理解を含む)
* Wildfly (またはJBoss AS) の基本的な運用経験 (起動・停止、デプロイなど)
* Wildfly CLIの基本的な操作 (jboss-cli.sh の実行経験)
* ログの基本的な概念 (ログレベル、ロガー、ハンドラなど)
Wildflyにおける未処理例外とロギングの課題
WildflyはJava EE/Jakarta EEアプリケーションサーバーとして、デプロイされたアプリケーションからの例外を適切に捕捉し、ロギングサブシステムを通じてログファイルに出力します。しかし、デフォルトのロギング設定では、しばしば以下のような課題に直面することがあります。
- 過剰なスタックトレース出力: 本番環境では、詳細すぎるスタックトレースがログファイルに大量に出力され、ディスク容量を圧迫したり、ログの可読性を低下させたりすることがあります。特に、頻繁に発生するビジネスロジック上の例外(例: 入力値バリデーションエラーなど)がすべて詳細なスタックトレース付きで出力されると、本当に重要なシステムエラーを見落とす原因にもなりかねません。
- 情報不足または不適切なフォーマット: 特定の例外に対しては、より詳細なコンテキスト情報が必要なのに、デフォルトのログフォーマットでは不足している場合があります。逆に、特定の情報が不要なのに必ず出力されてしまうこともあります。
- セキュリティ上の懸念: スタックトレースには、アプリケーションの内部構造やファイルパスなど、潜在的にセキュリティ上の機密情報が含まれる可能性があります。これらが不必要に外部に公開されるログファイルに含まれることは望ましくありません。
- デバッグの複雑さ: 開発環境では詳細な情報が必要でも、本番環境では最小限の情報に抑えたいといった、環境に応じた柔軟な設定が求められます。
WildflyはJBoss Loggingというロギングフレームワークを内部で利用しており、これはLog4jやLogbackのような一般的なロギングフレームワークを抽象化し、統一的なAPIを提供します。そして、Wildflyのロギングサブシステムを通じて、これらのロギング設定をCLIや管理コンソールから動的に変更できる強力な機能を持っています。この柔軟性を活用することで、上記のような課題を解決し、アプリケーションの運用を最適化することが可能になります。
Wildflyロギングサブシステムによる未処理例外ログの制御
ここでは、WildflyのCLIを使用して未処理例外ログの出力設定を具体的に変更する手順を解説します。主な目的は、ログのノイズを減らしつつ、必要な情報は確実に、適切なフォーマットで出力することです。
ステップ1: 現在のロギング設定を確認する
まず、現在のWildflyのロギングサブシステムの状態を確認することから始めます。jboss-cli.sh を起動し、接続します。
Bash/path/to/wildfly/bin/jboss-cli.sh --connect
接続後、ロギングサブシステムの全体像を確認します。
Cli[standalone@localhost:9990 /] /subsystem=logging:read-resource(recursive=true,include-runtime=true)
このコマンドは、ロガー、ハンドラ、フォーマッタなど、ロギングサブシステム内のすべての設定と現在のランタイム状態を詳細に表示します。特に注目すべきは、root-logger の設定、既存のlogger (特定のパッケージやクラスに対するロガー) および、periodic-rotating-file-handler や console-handler といったhandler の設定です。
デフォルトでは、未処理例外は通常、アプリケーションがデプロイされているWebコンテナやEJBコンテナのロガー、またはJBoss AS/Wildfly自体のサーバーログロガーによって捕捉され、server.log に出力されます。
例えば、root-logger の設定を確認するには次のようにします。
Cli[standalone@localhost:9990 /] /subsystem=logging/root-logger=ROOT:read-resource
この出力で、デフォルトのログレベル (level) や、関連付けられているハンドラ (handlers) を確認できます。
ステップ2: 未処理例外ログのロガーを特定し、ログレベルとスタックトレースの深度を調整する
未処理例外のログ出力を制御するには、対象となるロガーを特定し、その設定を変更する必要があります。多くの場合、アプリケーション固有のロガーを作成するか、または既存の汎用ロガーの設定を調整します。
アプリケーション固有のロガーを追加する
もし特定のパッケージ com.example.myapp 内で発生する例外のログのみを制御したい場合、そのパッケージのロガーを追加します。
Cli[standalone@localhost:9990 /] /subsystem=logging/logger=com.example.myapp:add(level=WARN, handlers=["CONSOLE", "FILE"])
level=WARN:com.example.myappから出力されるログレベルをWARN以上に制限します。これにより、INFOやDEBUGレベルのログは出力されなくなります。handlers=["CONSOLE", "FILE"]: このロガーのログを指定されたハンドラ(デフォルトのコンソールとファイルハンドラ)にルーティングします。
特定のロガーのスタックトレース出力深度を制御する
Wildfly 8以降では、stacktrace-element-count 属性を使用して、出力されるスタックトレースの最大行数を制御できます。これは、ログの冗長性を減らすのに非常に効果的です。
例として、org.jboss.as.server ロガー(Wildflyサーバーの主要なロガーの一つ)のスタックトレースをデフォルトの50行から10行に制限する場合を考えます。
Cli[standalone@localhost:9990 /] /subsystem=logging/logger=org.jboss.as.server:write-attribute(name=stacktrace-element-count, value=10)
同様に、アプリケーション固有のロガーに対しても設定できます。
Cli[standalone@localhost:9990 /] /subsystem=logging/logger=com.example.myapp:write-attribute(name=stacktrace-element-count, value=5)
stacktrace-element-count を 0 に設定すると、スタックトレースは全く出力されなくなります(注意して使用してください)。
グローバルなスタックトレース深度の調整
root-logger には stacktrace-element-count を設定できませんが、ロギングサブシステム全体のデフォルト値を設定することができます。
Cli[standalone@localhost:9990 /] /subsystem=logging:write-attribute(name=max-managed-marker-references, value=10) [standalone@localhost:9990 /] /subsystem=logging:write-attribute(name=max-managed-filter-references, value=10)
これらは、厳密にはスタックトレースの行数とは異なりますが、ロギングシステムの管理上の参照数に関する設定です。スタックトレースの行数制御はロガーごとに行うのが一般的です。
ステップ3: カスタムハンドラとロガーの関連付けで特定ログを分離する
特定の未処理例外だけを別のログファイルに出力したい、あるいは特別なフォーマットで出力したい場合は、カスタムハンドラを作成し、それを特定のロガーに関連付けます。
1. カスタムフォーマッタの作成
まず、例外用の特別なフォーマットを定義します。例えば、日時、ログレベル、ロガー名、メッセージ、そしてスタックトレースを短縮して出力するフォーマットを考えます。
Cli[standalone@localhost:9990 /] /subsystem=logging/pattern-formatter=UNHANDLED_EXCEPTION_FORMATTER:add(pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (Thread: %t) %s%e%n")
%d: 日時%-5p: ログレベル (左揃え5文字)%c: ロガー名%t: スレッド名%s: メッセージ%e: 例外スタックトレース
2. カスタムファイルハンドラの作成
次に、上記のフォーマッタを使用し、特定のファイルにログを出力するハンドラを作成します。例として、unhandled-exceptions.log というファイルにログを出力する periodic-rotating-file-handler を作成します。
Cli[standalone@localhost:9990 /] /subsystem=logging/periodic-rotating-file-handler=UNHANDLED_EXCEPTION_HANDLER:add(file={path="unhandled-exceptions.log", relative-to="jboss.server.log.dir"}, suffix=".yyyy-MM-dd", formatter=UNHANDLED_EXCEPTION_FORMATTER)
path="unhandled-exceptions.log": ログファイルのファイル名relative-to="jboss.server.log.dir": Wildflyのログディレクトリ (standalone/logなど) を基準とした相対パスsuffix=".yyyy-MM-dd": 日付でローテーションする際のサフィックスformatter=UNHANDLED_EXCEPTION_FORMATTER: 先ほど作成したフォーマッタを使用
3. ロガーへのカスタムハンドラの関連付けとフィルター設定
最後に、特定のロガー(例: com.example.myapp)にこのカスタムハンドラを関連付け、必要に応じてフィルターを設定します。ここでは、特定の例外クラス (java.lang.IllegalArgumentException など) のみがこのハンドラを通じて出力されるようにフィルターを設定する例を示します。
Cli[standalone@localhost:9990 /] /subsystem=logging/logger=com.example.myapp:add-handler(name=UNHANDLED_EXCEPTION_HANDLER) # 例: 特定の例外クラスのみをフィルターする(フィルターはハンドラまたはロガーに設定可能) # この例では、ハンドラに設定し、特定の文字列を含むログのみを許可する [standalone@localhost:9990 /] /subsystem=logging/periodic-rotating-file-handler=UNHANDLED_EXCEPTION_HANDLER:write-attribute(name=filter-spec, value="match(\".*IllegalArgumentException.*\")") # または、ログレベルでフィルターする場合 (既にロガーレベルで設定済みの場合は不要な場合も) # [standalone@localhost:9990 /] /subsystem=logging/periodic-rotating-file-handler=UNHANDLED_EXCEPTION_HANDLER:write-attribute(name=level, value=ERROR)
filter-spec には、JBoss Loggingがサポートするフィルター式を記述できます。match() は正規表現でのマッチングを行うフィルターです。これにより、com.example.myapp から発生し、かつIllegalArgumentExceptionを含むログメッセージのみが unhandled-exceptions.log に出力されるようになります。
ハマった点やエラー解決
設定が反映されない場合
Wildflyのロギング設定は、多くの場合動的に反映されますが、稀にサーバーのリロードが必要な場合があります。
Cli[standalone@localhost:9990 /] reload
または、サーバーを再起動することで確実に反映されます。
意図しないログが出力され続ける
- ロガーの階層: Javaのロガーは階層構造になっています。
com.example.myapp.SomeClassのログはcom.example.myappロガーで設定されていない場合、その親ロガー(最終的にはroot-logger)の設定が適用されます。特定のログを完全に制御するには、最も具体的なロガーパスで設定する必要があります。 - 複数のハンドラ: 一つのロガーが複数のハンドラにログをルーティングしている場合、それぞれのハンドラのフィルターやレベル設定も確認する必要があります。
use-parent-handlers: ロガーのuse-parent-handlers属性がtrueになっていると、親ロガーのハンドラにもログが転送されます。これをfalseに設定することで、そのロガーのログが親ロガーのハンドラに転送されるのを防ぎ、カスタムハンドラのみで処理させることができます。cli [standalone@localhost:9990 /] /subsystem=logging/logger=com.example.myapp:write-attribute(name=use-parent-handlers, value=false)
jboss-deployment-structure.xml を使ったアプリケーション固有のロギング設定
より高度な、デプロイメント固有のロギング設定を行いたい場合、アプリケーションの META-INF/ (WAR) または WEB-INF/ (EJB JAR) に jboss-deployment-structure.xml ファイルを配置することで、Wildflyのロギングサブシステム設定をオーバーライドできます。
例: 特定のモジュールのログレベルを変更する
Xml<!-- META-INF/jboss-deployment-structure.xml (EJB-JARの場合) --> <!-- WEB-INF/jboss-deployment-structure.xml (WARの場合) --> <jboss-deployment-structure> <deployment> <local-last value="true"/> <modules> <module name="deployment.my-application.war" slot="main" export="true"> <dependencies> <module name="org.apache.log4j" optional="true" /> </dependencies> </module> </modules> <subsystems> <subsystem name="logging"> <logger category="com.example.myapp.sensitive" level="ERROR"> <handlers> <handler name="UNHANDLED_EXCEPTION_HANDLER"/> </handlers> <filter-spec value="match(\".*SensitiveDataException.*\")"/> </logger> <root-logger level="INFO"/> </subsystem> </subsystems> </deployment> </jboss-deployment-structure>
この方法を使用すると、サーバー全体の設定を変更せずに、特定のアプリケーションのログ動作を詳細にカスタマイズできます。しかし、デプロイメントごとに設定を管理する必要があるため、複雑さが増す可能性もあります。
解決策
上記のハマりどころに対する解決策は、以下のポイントを意識することです。
- ロガーの適用範囲と階層を理解する: ログが出力される経路(どのロガーが捕捉し、どのハンドラに渡されるか)を正確に把握することが重要です。
use-parent-handlers=falseを適切に使うことで、ロガーが親にログを伝播するのを防ぎ、より限定的な制御が可能になります。 - CLIコマンドの慎重なテスト: 設定変更は開発環境やステージング環境で十分にテストし、意図した通りのログが出力されるか、余計なログが出ないかを確認してください。特に本番環境での
reloadや再起動は、影響を考慮して計画的に実行しましょう。 - フィルターの活用:
filter-specを使用することで、ログメッセージの内容に基づいてフィルターをかけることができます。特定のキーワードや例外クラス名を含むログのみを処理するといった高度な制御が可能です。 - 公式ドキュメントの参照: Wildflyのロギングサブシステムは非常に多機能です。より詳細な情報や特定のシナリオでの設定方法については、Wildflyの公式ドキュメントを参照することが最も確実です。
まとめ
本記事では、Wildfly環境におけるJavaアプリケーションの未処理例外ログを詳細に制御する方法 を解説しました。
- WildflyのロギングサブシステムCLI を活用することで、サーバーを停止することなく動的にロギング設定を変更できること。
- ロガー、ハンドラ、フォーマッタの組み合わせ により、ログレベルの調整、出力ファイルやフォーマットのカスタマイズ、そしてスタックトレースの出力深度制御など、柔軟なロギング設定が可能になること。
stacktrace-element-count属性やフィルター機能 を使用して、ログのノイズを削減しつつ、必要な情報を効果的に収集する具体的な手順。
この記事を通して、読者の皆様がWildflyアプリケーションの未処理例外ログに対する理解を深め、より効率的で運用しやすいロギング環境を構築できるようになることを願っています。適切なログ管理は、問題の早期発見、デバッグ時間の短縮、そしてシステムの安定運用に不可欠です。
今後は、LogbackやLog4j2などの外部ロギングフレームワークをWildflyに統合する方法や、ロギングデータを外部の監視ツール (Elasticsearch, Grafanaなど) と連携させる方法についても記事にする予定です。
参考資料
- Wildfly 27 Documentation - Subsystem Logging Configuration
- JBoss Logging Project
- How To Configure The Logging Subsystem In WildFly - JBoss EAP Community
