Java

Log4j2にDailyRollingFileAppenderは無い|RollingFileへの置き換えと移行手順

log4j 1.x の設定を Log4j2 に持ち込もうとして最初につまずくのが DailyRollingFileAppender です。Log4j2 にこの Appender は存在せず、そのまま設定ファイルに書いても認識されません。置き換え先は RollingFileTimeBasedTriggeringPolicy の組み合わせで、ロール周期は 1.x の DatePattern ではなく filePattern の日付書式が決めます。ただし log4j-1.2-api ブリッジ経由で 1.x の設定ファイルを読ませる場合だけは例外で、この Appender もサポート対象に含まれます。本記事は、その書き換え方と、既存の log4j.properties を捨てずに移行する公式ブリッジの使い方、移行後に起きやすい二重出力(additivity)とログレベルの設定までを、Apache 公式ドキュメントの記述に沿ってまとめます。

まとめ:DailyRollingFileAppenderからの移行で押さえる5点

  • Log4j2 に DailyRollingFileAppender は無い。置き換えは RollingFileTimeBasedTriggeringPolicy。公式の移行表でも equivalent は RollingFile とされている。
  • ロール間隔を決めるのは interval 単独ではなく、filePattern の最後の %d{...} の最小時間単位。日次なら %d{yyyy-MM-dd}
  • 世代数は DefaultRolloverStrategymax(既定 7)。log4j 1.x と同じ採番(新しいログが .1)にしたい場合だけ fileIndex="min" を指定する。
  • 設定ファイルを書き換える時間が取れないなら、log4j-1.2-api ブリッジと -Dlog4j1.compatibility=true で既存の log4j.properties をそのまま読ませられる(DailyRollingFileAppender もブリッジのサポート対象)。ただし公式は恒久策としていない。
  • 移行直後にログが二重に出るのは、Logger の Appender が親へ伝播する Log4j2 の既定動作。該当 Logger に additivity="false" を付ける。

Log4j2でDailyRollingFileAppenderが使えない理由と置き換え先

DailyRollingFileAppenderの現状:公式Javadocが認めるデータ損失とEOL

log4j 1.2 の Javadoc は DailyRollingFileAppender について 「synchronization issues and data loss を示すことが観測されている」と明記し、新規導入では別の実装を検討するよう促しています。その log4j 1.x 自体、2015年8月5日に Logging Services PMC が End of Life を宣言し、「2015年8月以降に報告された脆弱性は確認も修正もされない」と公式に告知されました。つまり DailyRollingFileAppender は、欠陥が知られたまま保守が止まった実装です。1.x 系に留まるなら保守フォークの reload4j という選択肢もありますが、Log4j2 へ移行すると決めたのであれば置き換え先は RollingFile 一択です。

置き換えの最小構成:RollingFile+TimeBasedTriggeringPolicy

1.x で File=app.logDatePattern='.'yyyy-MM-dd と書いていた日次ローテーションは、Log4j2 では次の log4j2.xml になります。現在ログを書き込むファイルが fileName、ロール後の退避名が filePattern で、両者が別物である点が 1.x との最大の違いです。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
  <Appenders>
    <RollingFile name="FILE"
                 fileName="/var/log/app/app.log"
                 filePattern="/var/log/app/app.%d{yyyy-MM-dd}.log">
      <PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n"/>
      <TimeBasedTriggeringPolicy/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="INFO">
      <AppenderRef ref="FILE"/>
    </Root>
  </Loggers>
</Configuration>

公式マニュアルは TimeBasedTriggeringPolicy を「filePattern の最小時間単位の値が変わったときにロールオーバーを起こす」と定義しています。日次でロールしたいのに filePattern%d を含め忘れると、ロールのたびに退避先が同じ名前になり上書きされます。ここは事故が起きやすく、真っ先に確認すべき箇所です。

DatePatternとfilePatternの変換対応表

1.x の DatePattern は SimpleDateFormat 書式で周期を表現していました。Log4j2 では同じ意図を filePattern%d{...} が担います。実際の書き換えでは次の対応で読み替えれば足ります。

ロール周期 log4j 1.x の DatePattern Log4j2 の filePattern(fileName=app.log の場合)
月次 ‘.’yyyy-MM app.%d{yyyy-MM}.log
週次 ‘.’yyyy-ww app.%d{yyyy-ww}.log
日次 ‘.’yyyy-MM-dd app.%d{yyyy-MM-dd}.log
半日 ‘.’yyyy-MM-dd-a app.%d{yyyy-MM-dd-a}.log
毎時 ‘.’yyyy-MM-dd-HH app.%d{yyyy-MM-dd-HH}.log
毎分 ‘.’yyyy-MM-dd-HH-mm app.%d{yyyy-MM-dd-HH-mm}.log

日次以下の周期では、ロールはサーバーの既定タイムゾーンの深夜に走ります。UTC 運用のコンテナで日本時間の 0 時に切りたい場合は、%d{yyyy-MM-dd}{Asia/Tokyo} のように %d 側でタイムゾーンを指定します。

RollingFileのローテーション設定:周期・世代数・圧縮・削除

ロール周期の決定要因:filePatternの最後の%dとinterval

interval の既定値は 1 で、公式の定義は「最後の %d{...} パターンで最も細かい時間単位を基準に、何単位ごとにロールするか」です。つまり interval は単位を持たず、単位は filePattern から取ります。%d{yyyy-MM-dd-HH}interval="6" なら 6 時間ごと。modulate を true にするとロール時刻が単位の境界(この例なら 0 時、6 時、12 時…)に揃いますが、公式が明記しているとおり効くのは interval が 1 より大きいときだけです。日次1回転の設定に modulate="true" を足しても意味はありません。

世代数と採番:DefaultRolloverStrategyのmaxとfileIndex

fileName を指定した RollingFile の既定戦略が DefaultRolloverStrategy です。max の既定値は 7、つまり %i が 7 を超えると古いものから捨てられます。注意すべきは採番の向きで、Log4j2 は既定で最も古いファイルが min、最新が max(fileIndex="max")。一方 log4j 1.x は常に新しいログが .1 になる方式でした。1.x と同じ見え方を維持したいなら、公式の移行手順どおり fileIndex="min" を明示します。

<RollingFile name="FILE"
             fileName="/var/log/app/app.log"
             filePattern="/var/log/app/app.%d{yyyy-MM-dd}.%i.log.gz">
  <PatternLayout pattern="%d{ISO8601} %-5level %logger{36} - %msg%n"/>
  <Policies>
    <TimeBasedTriggeringPolicy/>
    <SizeBasedTriggeringPolicy size="100 MB"/>
  </Policies>
  <DefaultRolloverStrategy max="30" fileIndex="min"/>
</RollingFile>

圧縮の有効化条件:退避ファイル名の拡張子とcompressionLevelの役割

圧縮の有効化は「退避ファイル名の拡張子」で決まります。.gz または .zip を filePattern の末尾に付ければそれだけで圧縮され、compressionLevel(既定 7)は圧縮の強さを調整するだけの属性です。.bz2.deflate.pack200.xz.zst も指定できますが、こちらは org.apache.commons:commons-compress の追加依存が必要で、compressionLevel は効きません。圧縮の有無は拡張子だけで決まるため、compressionLevel を書いても filePattern の末尾に拡張子が無ければ圧縮は行われません。

保持期間による削除:Deleteアクションの指定

%i ベースの世代管理は max で上限が効きますが、%d で日付ごとにファイルを増やす設定では世代数の概念がないため、古いファイルは放置すると溜まり続けます。保持期間で消すなら Delete アクションを書きます。次は 15 日より古い圧縮ログを削除する公式の書き方です。

<RollingFile name="FILE"
             fileName="/var/log/app/app.log"
             filePattern="/var/log/app/app.%d{yyyy-MM-dd}.log.gz">
  <PatternLayout pattern="%d{ISO8601} %-5level %logger{36} - %msg%n"/>
  <DefaultRolloverStrategy>
    <Delete basePath="/var/log/app">
      <IfFileName glob="app.*.log.gz"/>
      <IfLastModified age="P15D"/>
    </Delete>
  </DefaultRolloverStrategy>
  <TimeBasedTriggeringPolicy/>
</RollingFile>

IfFileNameglob が走査するのは basePath の直下だけです(maxDepth の既定値は 1)。日付ごとのサブディレクトリにログを振り分けている場合は、maxDepth="2"glob="*/app-*.log.gz" のように階層を明示しないと、古いファイルが削除されずに残り続けます。

時間ベースとサイズベースを併用するときは、公式が「入れ子にできる triggering policy は 1 つだけ」と定めているため、Policies 要素で束ねます。SizeBasedTriggeringPolicysize 既定値は 10 MB です。なお CronTriggeringPolicyTimeBasedTriggeringPolicy の同時使用は公式に「効果は未定義」と書かれています。両方書いてはいけません。

log4j 1.xからの移行:log4j-1.2-apiブリッジで既存設定を動かす

ブリッジの導入手順と除外が必要なJAR

アプリのコードが org.apache.log4j.Logger を直接呼んでいる場合、コードに手を入れずに Log4j2 のバックエンドへ載せ替えるのが log4j-1.2-api です。

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-1.2-api</artifactId>
  <scope>runtime</scope>
</dependency>

このブリッジは log4j 1.x のクラスを自前で提供して置き換えるため、log4j:log4jch.qos.reload4j:reload4jorg.slf4j:log4j-over-slf4j が classpath に残っていると衝突します。推移的依存で入り込みやすいので、mvn dependency:tree で確認して除外してください。

互換モードの指定:log4j.propertiesをそのまま読ませる設定

設定ファイルを書き換える工数が取れない段階では、実行時に 1.x の設定ファイルを変換して読み込む互換モードが使えます。システムプロパティ log4j1.compatibility(既定 false)を true にすると、Log4j2 が classpath 上の log4j-test.propertieslog4j-test.xmllog4j.propertieslog4j.xml を探索します。パスを直接指定する場合は log4j.configuration を併用します。

java -Dlog4j1.compatibility=true \
     -Dlog4j.configuration=file:/etc/app/log4j.properties \
     -jar app.jar

重要なのは、このモードでは DailyRollingFileAppender がサポート対象に含まれることです。公式のブリッジ対応表は ConsoleAppender、FileAppender、RollingFileAppender、AsyncAppender などと並べて DailyRollingFileAppender を挙げており、1.x 設定ファイル経由でのみ設定でき、内部では RollingFilefilePattern="app.%d{YYYY-MM-dd}" + TimeBasedTriggeringPolicy に写像されます。「Log4j2 では DailyRollingFileAppender は一切使えない」という説明を見かけますが、log4j2.xml に直接書けないだけで、ブリッジ経由の 1.x 設定なら動きます。

ブリッジの位置づけと撤去のタイミング

ここは立場を明確にしておきます。互換モードで動いたところで止めるべきではありません。公式マニュアルは「The Log4j 1 to Log4j 2 bridge is not conceived as a long term solution」と明言しており、ネイティブの 1.x コンポーネント利用は 2.17.2 以降の限定サポート、しかも 1.x 設定ファイル経由でしか設定できません。JSON 出力や非同期ロガーといった Log4j2 側の機能も、この経路では素直に使えません。リリースを止めないための一時措置として入れ、同じスプリント内で log4j2.xml へ変換し、ブリッジを外すまでを一区切りにしてください。log4j-1.2-api には、log4j.properties を log4j2.xml へ変換するユーティリティが同梱されています。

java org.apache.log4j.config.Log4j1ConfigurationConverter --in log4j.properties --out log4j2.xml

log4j2.xmlの基本:配置場所とログレベルの指定

log4j2.xmlの探索パスと明示指定

Log4j2 は classpath のルート(Maven なら src/main/resources/log4j2.xml)を自動で探します。外部ファイルを読ませるときは -Dlog4j2.configurationFile=/etc/app/log4j2.xml のように明示します。設定が読まれているか怪しいときは、Configuration 要素に status="warn" を付けると Log4j2 自身の内部ログが標準出力に出るため、どのファイルを読んだか、どの Appender の生成に失敗したかがその場で分かります。

ログレベル一覧と優先度の序列

Log4j2 の標準レベルは優先度の数値で順序が決まります。数値が小さいほど深刻です。

レベル 優先度 使いどころ
OFF 0 全ログ停止(特殊レベル)
FATAL 100 継続不能な障害
ERROR 200 要対処のエラー
WARN 300 異常の予兆
INFO 400 本番の既定レベル
DEBUG 500 障害調査用の文脈
TRACE 600 最も詳細な追跡
ALL Integer.MAX_VALUE 全ログ出力(特殊レベル)

公式は OFF と ALL を「特殊レベル」と位置づけ、ログイベントへの付与ではなく設定ファイルでの全停止・全出力に使うものとしています。また Log4j API はレベル名と優先度を定義するだけで「各レベルの用途は定義しない」と明記しており、FATAL と ERROR の線引きのようなルールはプロジェクト側で決める前提です。レベル名は大文字小文字を区別します。

Root・個別Logger・AppenderRefの3層でのlevel指定

レベルは Root、個別 Logger、AppenderRef の三層で指定できます。次の設定では、アプリのパッケージだけ DEBUG まで拾い、コンソールには WARN 以上しか流さない、という切り分けができます。

<Loggers>
  <Root level="INFO">
    <AppenderRef ref="CONSOLE" level="WARN"/>
    <AppenderRef ref="FILE"/>
  </Root>
  <Logger name="com.example.app" level="DEBUG"/>
</Loggers>

ログが二重に出る・出ない:additivityと切り分け手順

二重出力の停止:additivity=”false”による伝播の遮断

移行直後に「同じログが 2 行出る」のは設定ミスではなく Log4j2 の既定動作です。公式アーキテクチャの説明どおり、ログイベントは対象 LoggerConfig の Appender に加えて、その親の Appender にも渡ります(appender additivity)。個別 Logger にファイル出力を足すと、Root のコンソール出力にも同じ内容が流れる、という構図です。additivity の既定値は true で、これを Logger 宣言で false にすると伝播が止まります。

<Loggers>
  <Root level="INFO">
    <AppenderRef ref="CONSOLE"/>
  </Root>
  <!-- 監査ログは専用ファイルだけに出す -->
  <Logger name="com.example.audit" level="INFO" additivity="false">
    <AppenderRef ref="AUDIT_FILE"/>
  </Logger>
</Loggers>

未出力時の切り分け手順:status属性・レベル・フィルタ・依存関係

逆に何も出ない場合は、次の順で切り分けると原因にすぐ当たります。まず status="warn" で設定ファイルが読み込まれているかを確認する。読まれているのに出ないなら、Logger の level が出力しようとしているレベルより深刻側になっていないか、AppenderRef や Appender 側の ThresholdFilter が握りつぶしていないかを見ます。ThresholdFilter は指定レベル未満を捨てるフィルタで、次の書き方だと WARN 未満はこの Appender に届きません。

<Console name="CONSOLE" target="SYSTEM_OUT">
  <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
  <PatternLayout pattern="%d{ISO8601} %-5level %logger{36} - %msg%n"/>
</Console>

そもそも Log4j2 が動いていないケースでは、log4j-api だけが classpath にあり実装の log4j-core が入っていない、あるいは SLF4J 経由なのに log4j-slf4j2-impl が無い、という依存の欠落が典型です。

非同期ロギング:AsyncAppenderとAsyncLoggerの使い分け

2つの非同期方式の違い:キューとDisruptor

Log4j2 の非同期化には 2 系統あります。log4j 1.x 由来のキュー方式が AsyncAppender、LMAX Disruptor を使うロックフリー方式が AsyncLogger で、公式は後者を「キューではなく Disruptor に基づき、スループットが高くレイテンシが低い」と説明しています。

全ロガー非同期化の設定:contextSelectorの差し替え

全ロガーを非同期にする場合は、設定ファイルではなくシステムプロパティで context selector を差し替えます。

java -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector \
     -jar app.jar

Disruptor は設定ファイルの読み込み前に初期化されるため、リングバッファサイズなどのチューニングは log4j2.component.properties かシステムプロパティでしか行えません。また contextSelector で全非同期にしたうえで設定ファイルに AsyncRootAsyncLogger を書くと、公式が警告するとおり非同期バリアが二重に作られて性能が落ちます。どちらか一方にしてください。

非同期化を選ばない条件

非同期化を勧めない場面もはっきりしています。公式は、持続的な出力速度が Appender の処理能力を超えるとキューが埋まり、結局最も遅い Appender の速度に律速されるため「同期の方がスループットが高くレイテンシスパイクも少なくなる場合がある」と述べています。監査ログのように書き込み失敗を検知しなければならない用途も、例外通知が難しくなるため同期が無難です。公式はデメリットとして計算コストも挙げており、vCPU が 1 個しか割り当てられていないような環境では、別スレッドへ引き渡す処理そのものが不利に働きます。公式自身が「ベンチマークで有意差が出ないなら、単純な同期ロギングを推奨する」と明記している点は、判断基準として持っておく価値があります。

Log4Shell以降のバージョン運用:今どのバージョンを使うか

Log4Shell系4件のCVEの影響範囲と修正版

2021年12月の Log4Shell(CVE-2021-44228、CVSS 10.0)は、JNDI lookup を悪用したリモートコード実行でした。続く CVE-2021-45046(9.0)、CVE-2021-45105、CVE-2021-44832 を含め、公式のセキュリティページは影響範囲と修正版を次のように示しています。この 4 件はいずれも影響を受けるのが log4j-core のみで、log4j-api だけに依存するライブラリは対象外です(ただし後述の CVE-2026-49844 のように、log4j-api 側で見つかる不具合もあるため一般化はできません)。

CVE 内容 CVSS 修正版(Java 8以降)
CVE-2021-44228 JNDI lookupによるRCE 10.0 2.15.0
CVE-2021-45046 Thread Context Lookup経由のRCE 9.0 2.16.0
CVE-2021-45105 自己参照lookupによる無限再帰(DoS) 5.9 2.17.0
CVE-2021-44832 JDBC Appender経由のRCE 6.6 2.17.1

CVE-2021-44832 だけは修正版が 2.17.1 で、2.17.0 自体が影響範囲に含まれる点に注意してください。Java 7 系・Java 6 系を使っている場合の修正版はそれぞれ 2.12.4、2.3.2 です。

formatMsgNoLookupsが現在は無効な理由

当時の緊急回避策として広まった log4j2.formatMsgNoLookups=true は、いま追加しても効果がありません。CVE-2021-45046 の公式アドバイザリは「このシステムプロパティを設定する従来の緩和策では、この脆弱性は緩和されない」と明記しています。さらに 2.15.0 のリリースノートは「古い log4j2.formatMsgNoLookups は削除された」と述べ、2.16.0 ではメッセージ Lookup 自体が完全撤去、JNDI も既定で無効化されました。古い記事に従ってこのプロパティを足しているなら、それは対策ではなくノイズです。バージョンを上げてください。

2026年7月時点の最新版とLog4j 3の位置づけ

執筆時点(2026年7月14日)の最新安定版は 2.26.1(2026年6月29日リリース)で、保守ブランチとして 2.25.5(2026年7月1日)が並行しています。日付だけ見ると 2.25.5 が新しく見えますが、こちらは 2.25.x 系へのパッチです。Log4j 3 はまだ GA しておらず、公式ダウンロードページの最新は 3.0.0-beta3 のままなので、本番採用の判断はまだ先です。実行環境の要件は 2.x が Java 8 以上、3.x が Java 17 以上と公式に示されています。なお Log4Shell 系以外の CVE は現在も報告されています。直近の CVE-2026-49844 は log4j-api 側の不具合で、MapMessage に NaN や Infinity といった非有限の浮動小数点値が載ると MapMessage.asJson() がそのまま出力し、RFC 8259 に適合しない JSON になるというものです(影響 2.13.1〜2.25.4 および 2.26.0、修正版 2.25.5 / 2.26.1、CVSS 6.3)。「Log4Shell だけ塞げば終わり」ではなく、依存バージョンの定期更新が結局いちばん安いという結論は変わりません。最新の一覧は公式のセキュリティページで確認してください。

よくある質問

Log4j2でDailyRollingFileAppenderを指定するとどうなりますか?

log4j2.xml に <DailyRollingFileAppender> と書いてもプラグインが存在しないため認識されず、Appender が生成されません。status="warn" を有効にしていれば、起動時の内部ログにプラグインを解決できない旨が出ます。RollingFileTimeBasedTriggeringPolicy に書き換えるか、log4j-1.2-api ブリッジで 1.x 設定ファイルとして読ませてください。

ローテーションしたファイルの世代数はどこで決まりますか?

%i を使う場合は DefaultRolloverStrategymax(既定 7)です。%d で日付ごとにファイルを作る場合は世代の上限という概念がないため、Delete アクションと IfLastModified で保持期間を指定して削除します。

log4j2でadditivity=”false”にしてもログが二重に出るのはなぜですか?

additivity="false" は宣言した Logger の位置で伝播を止めるものです。同じロガー名に対する設定が複数ある、あるいは意図した Logger より下位のパッケージで別の Logger が定義されていると、そちらの経路で親まで届きます。また Root と個別 Logger の両方から同じ Appender を AppenderRef している場合も二重になります。まず出力元のロガー名を %logger でパターンに含めて特定してください。

log4j 1.xとLog4j2で設定の書き方は何が違いますか?

大きくは 3 点です。ロールの周期指定が DatePattern から filePattern%d に移ったこと、現在ファイル(fileName)と退避ファイル(filePattern)が分離されたこと、ローテーション時の採番の向きが逆(Log4j2 は既定で最新が最大番号)になったことです。採番だけは fileIndex="min" で 1.x と同じ挙動に戻せます。

SpringBootでLog4j2を使うにはどうしますか?

Spring Boot の既定ロガーは Logback なので、spring-boot-starter-logging を除外して spring-boot-starter-log4j2 を追加します。設定ファイルは log4j2-spring.xml を使うと、Spring のプロファイルやプロパティを参照できます。バージョン差異はSpring Boot 4の変更点も合わせて確認してください。

関連記事

資料請求

RELATED POSTS 関連記事