log4j 1.x の設定を Log4j2 に持ち込むと、最初に引っかかるのが DailyRollingFileAppender です。Log4j2 にこの Appender は無く、log4j2.xml に書いても Appender が作られません。置き換え先は RollingFile と TimeBasedTriggeringPolicy の組み合わせで、ロールの周期は 1.x の DatePattern ではなく filePattern の日付書式で決まります。例外は log4j-1.2-api ブリッジ経由で 1.x の設定ファイルを読ませる場合で、このときは DailyRollingFileAppender もそのまま動きます。
本記事の設定例は、2026年10月17日に log4j-core 2.26.1(2026年10月17日時点の最新安定版)と Java 25(Temurin 25)で実行し、出力されたエラー文・ファイル名をそのまま載せています。書き換え方、毎分ロールで挙動を確かめる手順、年末だけ表に出る大文字 YYYY の不具合、ブリッジで既存の log4j.properties を動かす方法、移行後に起きやすい二重出力とログレベルの設定までを扱います。
まとめ:DailyRollingFileAppenderからの移行で押さえる6点
- Log4j2 に DailyRollingFileAppender は無い。log4j2.xml に書くと起動時に
ERROR Appenders contains an invalid elementが出るだけでアプリは止まらず、ほかに有効な出力先が無ければログは1行も書かれない。置き換えは RollingFile + TimeBasedTriggeringPolicy。 - ロール周期を決めるのは filePattern の最後の
%d{...}の最小時間単位。日次なら%d{yyyy-MM-dd}。年を大文字の YYYY で書くと、2026年12月27日〜31日のログが2027年の名前で退避される(ja_JP で実測)。 - 連番
%iを使うときの世代数は DefaultRolloverStrategy の max(既定 7)。日付だけのファイル名には max の上限が効かないので、古いログは Delete アクションで消す。1.x と同じ採番(新しいログが小さい番号)にしたいときだけfileIndex="min"を付ける。 - 設定を書き換える時間が無ければ、log4j-1.2-api ブリッジを入れて
-Dlog4j.configuration=でファイルを指定するか、-Dlog4j1.compatibility=trueで classpath 上の log4j.properties を探させる。どちらか1つで 1.x 設定が動く。公式はこれを長期解としていない。 - 移行直後にログが二重に出るのは、Logger の出力が親の Appender にも渡る Log4j2 の既定動作。該当 Logger に
additivity="false"を付ける。 - 2026年10月時点の最新は 2.26.1、保守系は 2.25.5。ブリッジ自体にも CVE-2026-34479(2.25.4 で修正)が出ているため、互換モードを続ける間もブリッジの版は上げる。
以下、置き換えの書き方から順に、実行結果を添えて説明します。
Log4j2でDailyRollingFileAppenderが使えない理由と置き換え先
log4j2.xmlに書いたときの挙動:2.26.1の実際のエラー出力
1.x の感覚で log4j2.xml に <DailyRollingFileAppender name="FILE" fileName="app.log"/> と書いて起動すると、log4j-core 2.26.1 は次の2行をコンソールに出しました(Configuration に status 属性を書かなくても出ます)。
main ERROR Appenders contains an invalid element or attribute "DailyRollingFileAppender"
main ERROR Unable to locate appender "FILE" for logger config "root"
厄介なのは、アプリ自体は例外で止まらずに動き続けることです。この例では Root が参照する Appender がこれ1つだけなので、ログはどこにも書かれません。ほかに有効な Appender を参照していれば、そちらへの出力は続きます。起動ログを見ずにデプロイすると「ログファイルが作られない」という障害として後から気づくことになります。Log4j2 にはこの名前のプラグインが存在しないので、書き方を変えても通りません。
DailyRollingFileAppenderの現状:データ損失の注記と1.xのEOL
log4j 1.2 の Javadoc は DailyRollingFileAppender について「has been observed to exhibit synchronization issues and data loss」と書き、新規の利用を勧めていません。log4j 1.x 自体も Log4j のダウンロードページの版一覧で 2015年8月5日に EOL、最終版は 1.2.17 と示されています。1.x のまま延命するなら保守フォークの reload4j もありますが、Log4j2 へ移ると決めたなら置き換え先は RollingFile です。公式の Migrating from Log4j 1 の対応表も、DailyRollingFileAppender の Log4j2 側の equivalent を RollingFile としています。
置き換えの最小構成:RollingFileとTimeBasedTriggeringPolicy
1.x で File=app.log、DatePattern='.'yyyy-MM-dd と書いていた日次ローテーションは、log4j2.xml では次のとおりです。いま書き込むファイルが fileName、ロール後の退避名が filePattern で、両者を別々に書く点が 1.x との最大の違いです。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<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>
公式マニュアルの Rolling file appenders は TimeBasedTriggeringPolicy を、filePattern の最小時間単位の値が変わったときにロールする方式として定義しています。filePattern に %d を書き忘れると、2.26.1 では Pattern does not contain a date の例外で RollingFile の生成そのものが失敗しました。移行で最も事故が起きやすいのはこの1か所です。
log4j.propertiesの日次設定をlog4j2.propertiesへ書き換える例
「dailyrollingfileappender」で調べている方の手元には、たいてい次のような 1.x の properties があります。退避ファイル名は File の値の末尾に DatePattern を付けた app.log.2026-10-16 の形でした。
# log4j 1.x(移行前)log4j.properties
log4j.rootLogger=INFO, FILE
log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender
log4j.appender.FILE.File=/var/log/app/app.log
log4j.appender.FILE.DatePattern='.'yyyy-MM-dd
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c - %m%n
properties 形式のまま移るなら、ファイル名を log4j2.properties に変え、キーを Configuration file のページにある appender.<id>.* の形式へ書き直します。退避名を 1.x と同じ app.log.2026-10-16 に揃えたいなら、filePattern を app.log.%d{yyyy-MM-dd} にします。
# Log4j2(移行後)log4j2.properties
appender.0.type = RollingFile
appender.0.name = FILE
appender.0.fileName = /var/log/app/app.log
appender.0.filePattern = /var/log/app/app.log.%d{yyyy-MM-dd}
appender.0.layout.type = PatternLayout
appender.0.layout.pattern = %d{ISO8601} [%t] %-5level %logger{36} - %msg%n
appender.0.policy.type = Policies
appender.0.policy.0.type = TimeBasedTriggeringPolicy
rootLogger.level = INFO
rootLogger.appenderRef.0.ref = FILE
1.x の log4j.rootLogger=INFO, FILE の1行が、Log4j2 では rootLogger.level と rootLogger.appenderRef の2行に分かれます。書き換え漏れが出やすいのはこの行です。ConversionPattern の %p・%c・%m は Log4j2 の PatternLayout でもそのまま通じます。
DatePatternとfilePatternの対応表:月次から毎分まで
1.x の DatePattern が表していた周期は、Log4j2 では filePattern の %d{...} が担います。fileName=app.log の場合、次の対応で読み替えれば足ります(退避名の末尾に付ける .log や .gz は省略)。
| ロール周期 | log4j 1.x の DatePattern | Log4j2 の filePattern |
|---|---|---|
| 月次 | '.'yyyy-MM |
app.%d{yyyy-MM} |
| 週次 | '.'yyyy-ww |
app.%d{yyyy-ww} |
| 日次 | '.'yyyy-MM-dd |
app.%d{yyyy-MM-dd} |
| 半日 | '.'yyyy-MM-dd-a |
app.%d{yyyy-MM-dd-a} |
| 毎時 | '.'yyyy-MM-dd-HH |
app.%d{yyyy-MM-dd-HH} |
| 毎分 | '.'yyyy-MM-dd-HH-mm |
app.%d{yyyy-MM-dd-HH-mm} |
日次のロールは JVM の既定タイムゾーンの0時に起きます。UTC で動くコンテナで日本時間の0時に切りたいときは、%d{yyyy-MM-dd}{Asia/Tokyo} のように %d の後ろにタイムゾーンを書きます。
大文字YYYYの落とし穴:年末年始に1年ずれる退避ファイル名
年は必ず小文字の yyyy で書いてください。Pattern Layout のマニュアルは %d の書式を Java の DateTimeFormatter に準拠するとしており、DateTimeFormatter の大文字 Y は暦年ではなく「週基準の年」です。普段は暦年と同じ値になるため、年末年始の日付を指定しないテストでは見落とします。
どの日付で食い違うかを、log4j-core 2.26.1 の PatternLayout に日本時間正午のタイムスタンプを渡して確かめた結果が次の表です。
| 日付 | yyyy-MM-dd | YYYY-MM-dd(ja_JP・en_US) | YYYY-MM-dd(de_DE) |
|---|---|---|---|
| 2026-12-26(土) | 2026-12-26 | 2026-12-26 | 2026-12-26 |
| 2026-12-27(日) | 2026-12-27 | 2027-12-27 | 2026-12-27 |
| 2026-12-31(木) | 2026-12-31 | 2027-12-31 | 2026-12-31 |
| 2027-01-01(金) | 2027-01-01 | 2027-01-01 | 2026-01-01 |
ja_JP と en_US は週を日曜始まりで数えるため、2026年12月27日(日)から大晦日までの5日分が app.2027-12-27.log のような1年先の名前になります。週を月曜始まりで数える de_DE では逆に、2027年1月1日のログが app.2026-01-01.log という前年の名前になり、ちょうど1年前の元日のファイルと名前がぶつかります。どちらに転ぶかはサーバーのロケール次第で、同じ設定でも環境によって壊れる日が変わります。
注意したいのは、公式の移行ガイドの変換表自体が DailyRollingFileAppender の写像先を app.%d{YYYY-MM-dd} と大文字で書いている点です。表から写すときは小文字に直してください。既存の設定に大文字が残っていないかは、grep -rn '%d{YYYY' src/main/resources で一度に洗い出せます。
RollingFileのローテーション設定:周期・世代数・圧縮・削除
ロール周期を決めるもの:filePatternの最後の%dとinterval・modulate
TimeBasedTriggeringPolicy の interval は既定値 1 で、公式の定義は「最後の %d{...} の最も細かい時間単位を基準に、何単位ごとにロールするか」です。interval 自体は単位を持ちません。%d{yyyy-MM-dd-HH} と interval="6" なら6時間ごとになります。英語の解説には interval="1" を日次の指定のように書いているものがありますが、日次になるのは filePattern が日単位だからです。
modulate(既定 false)を true にすると、ロール時刻が単位の境界(この例なら0時・6時・12時)に揃います。公式が書くとおり効くのは interval が 1 より大きいときだけで、日次1回転の設定に足しても変わりません。
ロールが起きるタイミング:境界後の最初の書き込みと再起動時
ロールは時計で起きるのではなく、境界を越えた後の最初のログ書き込みで起きます。2026年10月17日 08:06:14 から1秒ごとに150行書く処理を、後述の毎分ロール設定で動かした結果は次のとおりでした。
| ファイル | 中身の時刻 |
|---|---|
| app.2026-10-17-08-06.log | 08:06:15〜08:06:59 |
| app.2026-10-17-08-07.log | 08:07:00〜08:07:58 |
| app.log(書き込み中) | 08:08:00〜08:08:53 |
退避名に付く時刻は「その分に書かれたログの時刻」で、ロールした時刻ではありません。続けて、プロセスを止めたまま 08:09 をまたぎ、08:10:05 に再起動しました。再起動後、前回の app.log は最終更新時刻に合わせて app.2026-10-17-08-08.log へ退避され、ログが1行も無かった 08:09 のファイルは作られませんでした。夜間にログを出さないバッチでは、0時を過ぎてもファイルが切り替わらず、ログの無い日はファイル自体が作られません。log4j 1.x の DailyRollingFileAppender も書き込み時に判定する作りで、この点の挙動は移行前後で変わりません。
世代数と採番:DefaultRolloverStrategyのmaxとfileIndex
fileName を書いた RollingFile の既定戦略は DefaultRolloverStrategy です。max の既定値は 7 で、%i が 7 を超えると古いものから消えます。採番の向きにも注意が要ります。Log4j2 の既定は 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>
日付と連番を併用するこの形では、max は「1日あたりの連番の上限」として効きます。時間とサイズの2つの条件を使うときは、公式が「入れ子にできる triggering policy は1つだけ」と定めているため Policies 要素で束ねます。SizeBasedTriggeringPolicy の size の既定値は 10 MB です。CronTriggeringPolicy と TimeBasedTriggeringPolicy の同時指定は、公式に「効果は未定義」とあるので書かないでください。
圧縮の有効化条件:退避ファイル名の拡張子とcompressionLevel
圧縮するかどうかは filePattern の末尾の拡張子で決まります。.gz か .zip を付ければ圧縮され、compressionLevel(既定 7)は強さを変えるだけの属性です。拡張子が無ければ compressionLevel を書いても圧縮されません。毎分ロールの検証でも、filePattern を gz.%d{yyyy-MM-dd-HH-mm}.log.gz にした Appender は、43行入りの退避ファイルを 217 バイトの gzip で出力しました。.bz2・.deflate・.pack200 も指定できますが、こちらは commons-compress の追加依存が必要で、compressionLevel は効きません。.xz と .zst はさらに、公式マニュアルのとおり commons-compress 以外の追加依存(XZ for Java、zstd-jni)が要ります。
保持期間による削除:DeleteアクションとmaxDepth
%d だけで日付ごとにファイルを増やす設定には世代数の上限がないため、放置すると古いファイルが溜まり続けます。保持期間で消すなら Delete アクションを書きます。次は、最終更新から15日以上たった圧縮ログを消す設定です。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>
Delete が走査するのは basePath の直下だけです(maxDepth の既定値は 1)。月ごとのサブディレクトリに振り分けている場合は maxDepth="2" を書かないと、古いファイルが消えずに残ります。Python 側で同じ保持ルールを作るときはクラスの分け方が違うので、PythonのRotatingFileHandlerでのログローテーション設定と並べて読むと、世代数と保持期間の決め方を揃えられます。
fileName省略時のDirectWriteRolloverStrategy:日付入りの名前での直接書き込み
「書き込み中のログも最初から app.2026-10-17.log の名前にしたい」なら、fileName を書かずに filePattern だけを指定します。公式マニュアルは、fileName が無いときは filePattern を現在のファイルにも使うとしており、既定戦略は DirectWriteRolloverStrategy になります。ロール時のリネームが起きないため、ログ収集エージェントがファイル名の変化を追い損ねる問題も起きにくくなります。
<RollingFile name="FILE"
filePattern="/var/log/app/app.%d{yyyy-MM-dd}.log">
<PatternLayout pattern="%d{ISO8601} %-5level %logger{36} - %msg%n"/>
<DirectWriteRolloverStrategy/>
<TimeBasedTriggeringPolicy/>
</RollingFile>
毎分ロールで日次設定を手元で検証する手順
検証用の依存関係と毎分ロールのlog4j2.xml
日次の設定は、本番に入れても翌朝まで結果が分かりません。周期は filePattern の最小単位で決まるので、検証のときだけ %d{yyyy-MM-dd-HH-mm} に変えれば毎分ロールになり、3分で挙動を確かめられます。依存は Maven Central の 2.26.1 を BOM で揃えます。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>2.26.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
src/main/resources/log4j2.xml には、本番用の設定から filePattern の日付書式だけを変えたものを置きます。
<Configuration>
<Appenders>
<RollingFile name="FILE"
fileName="logs/app.log"
filePattern="logs/app.%d{yyyy-MM-dd-HH-mm}.log">
<PatternLayout pattern="%d{HH:mm:ss} %-5level %msg%n"/>
<TimeBasedTriggeringPolicy/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="FILE"/>
</Root>
</Loggers>
</Configuration>
ログを出し続けるJavaクラスと確認する3点
1秒ごとに1行書くだけのクラスを150秒動かします。
package com.example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class RollCheck {
private static final Logger LOG = LogManager.getLogger(RollCheck.class);
public static void main(String[] args) throws InterruptedException {
// 1秒ごとに1行、合計150行(約2分半)を書き込む
for (int i = 0; i < 150; i++) {
LOG.info("tick {}", i);
Thread.sleep(1000);
}
}
}
mvn -q compile exec:java -Dexec.mainClass=com.example.RollCheck
ls -l logs/
見るのは3点です。分が変わるたびに退避ファイルが1本ずつ増え、app.log には直近の分の行だけが残っていること。退避名の時刻が中身の時刻と一致していること。年を含めるなら yyyy が小文字であること。前章の表と同じ結果になれば、filePattern を %d{yyyy-MM-dd} に戻して本番へ入れます。
log4j 1.xからの移行:log4j-1.2-apiブリッジで既存設定を動かす
ブリッジの導入手順と除外が必要な3つのJAR
アプリや依存ライブラリが org.apache.log4j.Logger を呼んでいる場合、コードを変えずに Log4j2 のバックエンドへ載せ替えるのが log4j-1.2-api です。自分のコードが旧 API を直接呼んでいるなら、コンパイルにもブリッジが必要なので scope は書かずに compile にします。
<!-- 自分のコードが org.apache.log4j.Logger を直接呼ぶ場合(compile) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
</dependency>
旧 API を呼んでいるのがコンパイル済みの依存ライブラリだけなら、公式の例どおり <scope>runtime</scope> で足ります。このブリッジは log4j 1.x のクラスを自前で提供して置き換えるため、公式の移行ガイドは log4j:log4j、ch.qos.reload4j:reload4j、org.slf4j:log4j-over-slf4j の3つと共存できないとしています。推移的依存で入り込みやすいので、mvn dependency:tree -Dincludes=log4j:log4j で確認して除外してください。依存の奥に残った 1.x を CI で検出し続けたい場合は、SnykのCLIでの脆弱性スキャンとCI組み込みのように依存スキャンを仕組みに入れておくと漏れません。
互換モードの有効化:log4j.configurationとlog4j1.compatibilityの違い
設定を書き換える工数が取れない段階では、1.x の設定ファイルを実行時に変換して読み込む互換モードが使えます。公式の移行ガイドは、次のどちらか1つを設定すればよいとしています。
log4j.configuration:1.x 設定ファイルの場所を直接指定する(拡張子は .properties か .xml)。log4j1.compatibility=true:classpath 上の log4j-test.properties、log4j-test.xml、log4j.properties、log4j.xml を探させる。
2.26.1 で DailyRollingFileAppender を使う log4j.properties を用意し、3通りで起動して確かめました。-Dlog4j.configuration=file:log4j.properties だけを付けた場合は、compatibility の指定が無くても 1.x 設定が読まれ、ログが書かれました。classpath に log4j.properties を置いただけでは読まれず、-Dlog4j1.compatibility=true を足すと読まれました。外部ファイルを指定する運用なら前者、WAR の中に log4j.properties を同梱している構成なら後者が必要です。
java -Dlog4j.configuration=file:/etc/app/log4j.properties -jar app.jar
互換モードでは DailyRollingFileAppender もそのまま動きます。公式のブリッジ対応表は ConsoleAppender、FileAppender、RollingFileAppender などと並べて DailyRollingFileAppender を挙げています。実際に DatePattern='.'yyyy-MM-dd-HH-mm で動かすと、退避名は app.log.2026-10-17-08-06 のように 1.x と同じ形で作られました。「Log4j2 では DailyRollingFileAppender は一切使えない」という説明を見かけますが、正確には log4j2.xml に書けないだけで、ブリッジ経由の 1.x 設定なら動きます。
Log4j1ConfigurationConverterで変換したときの出力
log4j-1.2-api には、log4j.properties を log4j2.xml へ変換するユーティリティが同梱されています。classpath に log4j-api・log4j-core・log4j-1.2-api の3つを入れて実行します。
java -cp "lib/*" org.apache.log4j.config.Log4j1ConfigurationConverter --in log4j.properties --out log4j2.xml
前述の検証で使った log4j.properties(File=logs/app.log、DatePattern='.'yyyy-MM-dd-HH-mm、ConversionPattern=%d{HH:mm:ss} %-5p %c - %m%n)を 2.26.1 で変換した結果が次です(見やすく改行しています)。
<Configuration name="Log4j1">
<Appenders>
<RollingFile name="FILE" fileName="logs/app.log"
filePattern="logs/app.log%d{'.'yyyy-MM-dd-HH-mm}">
<PatternLayout pattern="%d{HH:mm:ss} %-5v1Level %c - %m%n"/>
<Policies><TimeBasedTriggeringPolicy modulate="true"/></Policies>
<DefaultRolloverStrategy max="2147483647" fileIndex="min"/>
</RollingFile>
</Appenders>
<Loggers><Root level="INFO"><AppenderRef ref="FILE"/></Root></Loggers>
</Configuration>
変換結果はそのまま本番に置かず、3か所を手で直してください。max="2147483647" は事実上の無制限なので、保持期間の Delete を足す。%-5v1Level は log4j-1.2-api の中にある変換子で、ブリッジを外すと解決できなくなるので %-5level に戻す。modulate="true" は interval が 1 のままなら効果が無いので消してかまいません。日付書式は DatePattern から写されるため小文字の yyyy が保たれますが、DatePattern 側が大文字なら大文字のまま移ります。
ブリッジの位置づけ:一時措置としての導入と撤去計画
互換モードで動いたところで止めないでください。公式の移行ガイドは「The Log4j 1 to Log4j 2 bridge is not conceived as a long term solution」と書き、1.x への依存を無くしたらブリッジを外すよう求めています。1.x のネイティブ部品をブリッジで使えるのも 2.17.2 以降の限定サポートで、設定経路は 1.x 設定ファイルに限られます。ログを JSON にして集約基盤へ送る計画があるなら、構造化ログの仕組みと実装の考え方を先に決め、その出力形式で log4j2.xml を書き起こすほうが手戻りが少なく済みます。ブリッジはリリースを止めないための一時措置として入れ、変換と撤去を同じ計画に入れてください。
log4j2.xmlの基本:配置場所とログレベルの指定方法
log4j2.xmlの探索場所とconfigurationFileによる明示指定
Log4j2 は classpath のルート(Maven なら src/main/resources/log4j2.xml)を自動で探します。外部ファイルを読ませるなら -Dlog4j2.configurationFile=/etc/app/log4j2.xml で明示するのが確実です。設定が読まれているか怪しいときは、-Dlog4j2.debug=true を付けて起動します。内部ログ(StatusLogger)が TRACE になり、2.26.1 では Reconfiguration complete ... at URI の行に実際に読んだファイルのパスが出ました。Configuration 要素の status 属性は 2.24.0 で非推奨になり、公式は log4j2.statusLoggerLevel プロパティへの置き換えを求めています。
ログレベル一覧:OFFからALLまで8段階の優先度
Log4j2 の標準レベルは優先度の数値で順序が決まり、数値が小さいほど深刻です。
| レベル | 優先度 | 使いどころ |
|---|---|---|
| OFF | 0 | 全ログ停止(特殊レベル) |
| FATAL | 100 | 継続不能な障害 |
| ERROR | 200 | 要対処のエラー |
| WARN | 300 | 異常の予兆 |
| INFO | 400 | 本番の既定レベル |
| DEBUG | 500 | 障害調査用の文脈 |
| TRACE | 600 | 最も詳細な追跡 |
| ALL | Integer.MAX_VALUE | 全ログ出力(特殊レベル) |
公式の Levels のページは OFF と ALL を特殊レベルとし、ログイベントに付けるのではなく設定で全停止・全出力にするためのものと位置づけています。Log4j API はレベル名と優先度を決めるだけで、各レベルをどう使うかは定めていません。FATAL と ERROR の線引きはプロジェクト側で決めます。
Root・個別Logger・AppenderRefの3層でのlevel指定
レベルは Root、個別 Logger、AppenderRef の3か所で指定できます。次の設定では、アプリのパッケージだけ 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行出る原因は、多くが親への伝播です。公式のアーキテクチャの説明のとおり、ログイベントは対象の LoggerConfig の Appender に加えて、親の Appender にも渡ります。個別 Logger にファイル出力を足すと、Root のコンソールにも同じ内容が流れる構図です。伝播自体は既定動作(additivity の既定値は true)ですが、二重になるのは子と親の両方に Appender を付けた設定の組み合わせによるものです。子の 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>
何も出ないときの切り分け:内部ログ・レベル・ThresholdFilter・依存
逆に何も出ないときは、次の順に見ると早く当たります。最初に -Dlog4j2.debug=true の内部ログで設定ファイルが読まれ、Appender が作られているかを確かめる。読まれているのに出ないなら、Logger の level が出したいレベルより深刻側になっていないか、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 だけがあって実装の log4j-core が入っていない、SLF4J 経由なのに log4j-slf4j2-impl が無い、という依存の欠落が典型です。
非同期ロギング:AsyncAppenderとAsyncLoggerの使い分け
2つの非同期方式の違いと全ロガー非同期化の設定
Log4j2 の非同期化には2系統あります。1.x 由来のキュー方式が AsyncAppender、LMAX Disruptor を使うのが AsyncLogger で、公式の Asynchronous loggers のページは後者をキューではなく Disruptor に基づく方式としています。全ロガーを非同期にするときは、設定ファイルではなくシステムプロパティで context selector を差し替えます。
java -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector \
-jar app.jar
この方式は log4j-core とは別に com.lmax:disruptor の依存が必要です。contextSelector で全非同期にしたうえで設定ファイルに AsyncRoot や AsyncLogger を書くと、非同期の処理が二重になります。どちらか一方にしてください。
非同期化を選ばない条件:出力過多・監査ログ・単一vCPU
非同期化を勧めない場面ははっきりしています。公式は、出力の速度が Appender の処理能力を上回り続けるとキューが埋まり、最も遅い Appender の速度に律速されるため、同期のほうがスループットが高くなる場合があるとしています。書き込み失敗を検知しなければならない監査ログも同期にします。ただし同期でも Appender の ignoreExceptions は既定で true なので、例外を呼び出し元に返すには Appender に ignoreExceptions="false" を付け、ログ出力の呼び出し側で例外を処理します。vCPU が1つしか無い環境では、別スレッドへ渡す処理そのものが負担になります。ベンチマークで差が出ないなら同期のままにする、というのが公式の推奨です。
Log4Shell以降のバージョン運用:CVEごとの修正版と採用する版
Log4Shell系4件のCVEと修正版
2021年12月の Log4Shell(CVE-2021-44228)は、JNDI lookup を悪用したリモートコード実行でした。続く3件を含め、公式のセキュリティページが示す影響と修正版は次のとおりです。4件とも影響を受けるのは log4j-core です。
| CVE | 内容 | CVSS | 修正版(Java 8以降) |
|---|---|---|---|
| CVE-2021-44228 | JNDI lookupによるRCE | 10.0 | 2.15.0 |
| CVE-2021-45046 | スレッドコンテキスト経由の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 は影響範囲に入ります。CVE レコードは「2.0-beta7 から 2.17.0 まで(2.3.2 と 2.12.4 を除く)」が影響を受け、2.17.1・2.12.4・2.3.2 で修正としています。Java 7 系・Java 6 系で4件すべてを塞いだ版は、それぞれ 2.12.4 と 2.3.2 です。当時の回避策として広まった log4j2.formatMsgNoLookups=true は、CVE-2021-45046 の公式アドバイザリが「この緩和策では防げない」としており、2.15.0 で設定自体が削除されました。いまから足しても意味がありません。
2.25.3以降に直ったCVEとブリッジ利用者への影響
Log4Shell 系の後も CVE は続いています。GitHub Advisory で確認できる 2.17.1 以降の主なものは次のとおりです。
| CVE | 対象 | 内容 | 修正版 |
|---|---|---|---|
| CVE-2025-68161 | log4j-core | Socket AppenderのTLSホスト名検証漏れ | 2.25.3 |
| CVE-2026-34477 | log4j-core | verifyHostName属性の無視 | 2.25.4 |
| CVE-2026-34478 | log4j-core | Rfc5424LayoutのCRLF注入 | 2.25.4 |
| CVE-2026-34479 | log4j-1.2-api | Log4j1XmlLayoutの禁止文字 | 2.25.4 |
| CVE-2026-34480 | log4j-core | XmlLayoutの禁止文字 | 2.25.4 |
| CVE-2026-34481 | JSON Template Layout | NaN・Infinityの不正JSON | 2.25.4 |
| CVE-2026-49844 | log4j-api | MapMessage.asJson()の不正JSON | 2.25.5 / 2.26.1 |
DailyRollingFileAppender をブリッジで動かしている現場が見落とせないのは CVE-2026-34479 です。対象は log4j-1.2-api、つまり前章のブリッジそのもので、影響範囲は 2.7 以上 2.25.4 未満です。発現するのは 1.x 設定で Log4j1XmlLayout(1.x の XMLLayout)を使っている場合で、本記事の PatternLayout の設定はこの不具合の対象ではありません。ブリッジは一度入れると版が据え置かれがちなので、互換モードを続ける間も log4j-core と同じ版に揃えてください。最後の CVE-2026-49844 は log4j-api 側の不具合で、2.26.0 も影響を受けます。log4j-api だけに依存するライブラリも対象外とは言えません。Java 全体の脆弱性情報の追い方はJava脆弱性の一覧と対策で整理しています。
2026年10月時点の最新版2.26.1とLog4j 3の位置づけ
公式のリリースノートで、最新の安定版は 2.26.1(2026年6月29日)、保守系の最新は 2.25.5(2026年7月1日)です。日付だけ見ると 2.25.5 が新しく見えますが、こちらは 2.25.x 系へのパッチです。Maven Central の log4j-core のメタデータも 2026年7月6日の更新が最後で、10月17日時点でこれより新しい 2.x はありません。Log4j 3 は 3.0.0-beta3(2024年11月9日)のまま正式版が出ていないので、本番は 2.26.x で組みます。
使っているLog4jのバージョンを確認する方法
Maven のプロジェクトなら、推移的依存まで含めて次のコマンドで一覧できます。1.x が残っていないかは -Dincludes=log4j:log4j に変えて確かめます。
mvn dependency:tree -Dincludes=org.apache.logging.log4j
ビルド済みの JAR しか手元に無い場合は、JAR 内の MANIFEST.MF を読みます。log4j-core-2.26.1.jar では Implementation-Version: 2.26.1 と書かれていました。
unzip -p log4j-core-*.jar META-INF/MANIFEST.MF | grep Implementation-Version
fat JAR に埋め込まれている場合はファイル名から分からないので、unzip -l app.jar | grep log4j で同梱物を一覧してから中身を読みます。
移行方式の判断:設定書き換え・ブリッジ継続・1.x残留の採用条件
3つの移行方式の比較:作業量と残るリスク
DailyRollingFileAppender を抱えたシステムが取れる道は、実質3つです。
| 方式 | 作業量 | 残るリスク | 位置づけ |
|---|---|---|---|
| log4j2.xml へ書き換え | 設定の書き直しと検証 | 小さい | 本命 |
| ブリッジで 1.x 設定を継続 | 依存の追加のみ | 機能制限と版の追随漏れ | 一時措置 |
| reload4j で 1.x に残留 | 依存の差し替えのみ | Log4j2 の機能を使えない | 延命 |
新規開発や、今後も機能追加が続くシステムなら、最初から log4j2.xml へ書き換えます。ブリッジで動かして終わりにする案は採りません。ブリッジを使うのは、リリース日が迫って設定の検証時間が取れない場合だけで、次のリリースで外すと決めたうえで入れます。reload4j での残留は、数年以内の廃止やリプレイスが決まっていて、ログ周りに手を入れる意味が薄いシステムに限ります。
ログ設定の移行をJDK更新と同時に進める条件
log4j 1.x が残るシステムでは、JDK やフレームワークの版も古いままになっていないかを先に確かめます(java -version と mvn dependency:tree で揃って確認できます)。どちらも古い場合、ログだけ先に直すと JDK を上げるときに依存の再調整がもう一度発生し、全部を一度に上げると障害時の切り分けが難しくなります。JDK の更新を予定しているなら同じ計画にログ移行を入れ、リリースは「ログ移行」「JDK 更新」の順に分けて出すのが手戻りの少ない進め方です。JDK とフレームワーク側の段取りはJavaマイグレーションの進め方にまとめています。古い Java システムの依存整理から移行・リプレイスまでを外部に任せたい場合は、一創のシステムマイグレーション・リプレイス支援で現状調査から対応しています。
よくある質問
Log4j2でDailyRollingFileAppenderを指定するとどうなりますか?
log4j2.xml に書いた場合、2.26.1 では起動時に Appenders contains an invalid element or attribute "DailyRollingFileAppender" と Unable to locate appender のエラーが出て、その Appender は作られません。アプリは止まらずに動き続けるため、ログが書かれないまま気づかないことがあります。RollingFile + TimeBasedTriggeringPolicy に書き換えるか、log4j-1.2-api ブリッジで 1.x 設定ファイルとして読ませてください。
ローテーションしたファイルの世代数はどこで決まりますか?
%i を使う場合は DefaultRolloverStrategy の max(既定 7)です。%d で日付ごとにファイルを作る場合は世代の上限という考え方が無いため、Delete アクションと IfLastModified で保持期間を指定して消します。app.%d{yyyy-MM-dd}.%i.log.gz のように日付と連番を併用するなら、max は1日あたりの連番の上限として効きます。
log4j2でadditivityをfalseにしてもログが二重に出るのはなぜですか?
additivity="false" は、宣言した Logger の位置で親への伝播を止めます。Root と子 Logger が同じ Appender を参照していても、子が false なら出力は1回です(2.26.1 で確認)。それでも二重になるなら、そのログに実際に適用されている LoggerConfig が想定と違う可能性があります。たとえば false を付けた Logger より下位のパッケージに別の Logger があり、そちらが既定の true のまま親へ伝播している構成です。PatternLayout に %logger を入れて出力元のロガー名を特定し、その名前に一致する最も近い Logger 宣言から親までの経路をたどってください。
log4j 1.xとLog4j2で設定の書き方は何が違いますか?
大きな違いは3点です。ロール周期の指定が DatePattern から filePattern の %d に移ったこと、書き込み中のファイル(fileName)と退避ファイル(filePattern)が分かれたこと、採番の向きが逆(Log4j2 の既定は番号が大きいほど新しい)になったことです。採番は fileIndex="min" で 1.x と同じにできます。properties 形式を続ける場合も、キーは appender.<id>.* の形式へ書き直しが必要です。
Spring BootでLog4j2を使うにはどうしますか?
Spring Boot の既定ロガーは Logback なので、Spring Boot 公式の Logging の手順どおり spring-boot-starter-logging を除外し、spring-boot-starter-log4j2 を追加します。設定ファイルを log4j2-spring.xml という名前にすると、Spring のプロファイルやプロパティを参照できます。Spring Boot 側の版の違いはSpring Boot 4の変更点もあわせて確認してください。