Java: えっ、まだ Log4j 1.2 使ってんの?

ないわー。なんで Log4j 2.0 使ってないの?

公式サイトとかあるの?

あるよ。

Apache Log4j 2
http://logging.apache.org/log4j/2.x/

何が変わったの?

ロギングライブラリとしての基本的な考え方は変わっていない。Logger のインスタンスをクラスごとに作ったり、Appender を関連付けてコンソールやファイルに出力したり、trace/debug/info/warn/error といったログレベルを使い分けたり。
けれども、全体的に見直しがはかられて、「いまどきのロギングライブラリ」になっている。
というか、一言で言うと、SLF4J っぽくなっている。

おさらい: Log4j 1.2 ではこうやってた

まずは Log4j の jar ファイルを持ってくる。Maven を使っている場合は、POM に依存ライブラリとして以下を追加していた。

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

次は設定ファイルだ。log4j.xml という名前の XML ファイルを作って、クラスパスが通っている場所に置くのがいちばん手早い。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p [%t]: %m%n" />
		</layout>
	</appender>
	
	<root>
		<level value="debug" />
		<appender-ref ref="console" />
	</root>
</log4j:configuration>

準備ができたらいよいよ Java でコーディング。Logger のインスタンスを作って、ログ出力用のメソッドにメッセージを渡す。

import org.apache.log4j.Logger;

public class Main {
	private static final Logger LOG = Logger.getLogger(Main.class);

	public static void main(String[] args) {
		MemoryUsage usage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
		LOG.info("ヒープ使用量: " + usage.getUsed() + "/" + usage.getMax());
	}
}

……。

いまどきこんなログ出力してたらいけませんよ。

Log4j 2.0 だとどうなるの?

大枠は変わらない。ここでもまずは参照する jar を持ってこよう。Maven の場合は、こんな感じで 2 つ追加する。

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.0-beta3</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.0-beta3</version>
</dependency>

次に設定ファイル。これまでとは微妙に違う、log4j2.xml というファイル名で作成する。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
	<appenders>
		<Console name="console">
			<PatternLayout pattern="%-5level [%t]: %msg%n" />
		</Console>
	</appenders>
	
	<loggers>
		<root level="debug">
			<appender-ref ref="console" />
		</root>
	</loggers>
</configuration>

appenders 要素と loggers 要素が新たに登場している (ともに複数形の -s が付いていることに注意)。一段インデントが深くなったけど、かわりに個々のアペンダやロガーは Log4j 1.2 のときより少し短く書けるようになった。

最後に Java のソース。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Main {
	private static final Logger LOG = LogManager.getLogger(Main.class);

	public static void main(String[] args) {
		MemoryUsage usage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
		LOG.info("ヒープ使用量: {}/{}", usage.getUsed(), usage.getMax());
	}
}

今までと変わったのは、以下のポイント。

  • パッケージが変わった (org.apache.log4j → org.apache.logging.log4j)
  • Logger のインスタンスを取得する static メソッドの getLogger は、Logger にはなくなった
    • 1.2 のときは Logger と LogManager の両方にあったけど、Logger の 方を使ってた人が多いのでは?
  • info() などのログ出力メソッドで、プレースホルダと可変長引数が使えるようになった
    • {} の部分に引数が埋め込まれる

新メソッドはじめました

Log4j 2.0 では、これまでのフィードバックを反映して他にも様々な新機能が用意されている。

詳しくは公式サイトのドキュメントを眺めてもらうとして、ここでは、すぐに使えそうな新設のログ出力メソッドについて紹介しておく。

几帳面な人は、メソッドの入り口と出口で TRACE レベルのログを出す、ということをやっているかもしれない。今までは trace() を使っていたと思うが、Log4j 2.0 では専用のログ出力メソッドが新設された。

	public int add(int a, int b) {
		LOG.entry(a, b);
		
		int sum = a + b;
		
		return LOG.exit(sum);
	}

こんな感じで、入り口で entry() にメソッドの引数を渡したり、戻り値を return する前に exit() を通すと、下記のような定型のログが出力される。なお、exit() は、ログを出力するだけで、渡された引数をそのまま返すメソッドになっている。

TRACE [main]:  entry parms(100, 200)
TRACE [main]:  exit with (300)

同様に、例外を catch したときに ERROR レベルのログを出すための catching() とか、例外を投げるときに使うための throwing() とかもある。

あ、そうそう。Log4j 2.0 の PatternLayout では、デフォルトだと例外のメッセージやスタックトレースが出力されないので注意したい。下記のように、パターンの最後に %ex と書いておくと、1.2 の時と同様にメッセージとスタックトレースが出るようになる。

			<PatternLayout pattern="%-5level [%t]: %msg%n%ex" />

なんかめんどくさくなったのか、と思われたかもしれないが、その代わり、スタックトレースの行数を指定できるようになっている。例えば次のように {5} を付けると、例外メッセージとスタックトレースあわせて最初の 5 行だけ出力されるようになる。

			<PatternLayout pattern="%-5level [%t]: %msg%n%ex{5}" />

互換性がないのがいやな場合は?

ここまで見てきたように、Log4j 2.0 は従来の Log4j 1.2 と互換性がない。パッケージ名が変わっていたり、なくなったメソッドがあったり。MDC と NDC が統合されて ThreadContext になった、みたいな変更もある。

Log4j 2.0 に乗り換えてみたいけど、Log4j 1.2 で書いてきた既存のコードを全部直すのはちょっとなぁ……と思われるかもしれない。が、そんなあなたのために、Log4j 2.0 に Log4j 1.2 互換の API をラッパーとしてかぶせることもできるようになっている。

使い方は互換 API の jar を追加するだけ。Maven の場合は、Log4j 2.0 のライブラリに加えて、下記を追加するだけでいい。

<dependency>
    <groupId>org.apache.logging.log4j.adapters</groupId>
    <artifactId>log4j12-api</artifactId>
    <version>2.0-beta3</version>
</dependency>

ただし、あくまで Log4j 1.2 の皮を被るだけなので、設定ファイルは Logj 2.0 の形式で用意する必要がある。

おわりに

POM の記述例を見て気付かれたかもしれないが、実はまだ Log4j 2.0 はベータ版。そのため、製品にいきなり使うのは不安、ということはあるかもしれない。しかし、現状でもふつーに動いているので、少なくとも個人で書くコードなら Log4j 2.0 の利用をお勧めしたい。
可変長引数や高パフォーマンスを求めて SLF4J を使ったことのある人は、Log4j 2.0 を見て「うわ、SLF4J にそっくり!」と感じるだろう。API とロガー実装で jar が分離された点とか、ログメッセージのプレースホルダが {} なところとか。言い換えると、そういう人は Log4j 2.0 へ乗り換えても苦労しないと思う。
「えっ、じゃぁ SLF4J でいいじゃん」って? それはまぁ正しい。が、その一方で、本当は SLF4J を使いたいんだけど職場力学的に「SLF4J にしよう」と言えず悶々としている人には朗報だ。これからは、「Log4J のバージョンを最新のやつにしましょう」と言えばすむのだから。