解決編: logbackで同一レベルの複数ログを異なる出力先に出す

前回の「疑問メモ: logbackで同一レベルの複数ログを異なる出力先に出したい」の解決編、「同一レベルのログを用途に応じて出し分ける」話の続きです。

師匠から参考になる情報をいただき、無事実現できました。ありがとうございます。

JaninoEventEvaluator を活用すればよいのですね。

JaninoEventEvaluator を使うには Janino libraryが追加で必要なので、準備します。

設定ファイル(logback.xml)を次のようにします。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<appender name="FILE" class="ch.qos.logback.core.FileAppender">
		<filter class="ch.qos.logback.core.filter.EvaluatorFilter">      
			<evaluator>
				<expression>return message.contains("> ");</expression>
			</evaluator>
			<OnMismatch>ACCEPT</OnMismatch>
			<OnMatch>DENY</OnMatch>
		</filter>
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<level>INFO</level>
			<onMatch>DENY</onMatch>
			<onMismatch>ACCEPT</onMismatch>
		</filter>
		<File>sample.log</File>
		<Append>true</Append>
		<encoder>
			<pattern>%d %5p %c{1} - %m%n</pattern>
		</encoder>
	</appender>
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<filter class="ch.qos.logback.core.filter.EvaluatorFilter">      
			<evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
				<expression>return message.contains("> ");</expression>
			</evaluator>
			<OnMismatch>DENY</OnMismatch>
			<OnMatch>ACCEPT</OnMatch>
		</filter>
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<level>INFO</level>
			<onMatch>ACCEPT</onMatch>
			<onMismatch>DENY</onMismatch>
		</filter>
		<encoder>
			<Pattern>%msg%n</Pattern>
		</encoder>
	</appender>
	<root level="DEBUG">
		<appender-ref ref="STDOUT" />
 		<appender-ref ref="FILE" />
	</root>
</configuration>

今回は、メッセージに「> 」という文字列が含まれていればコンソールに出力し、そうでなければファイルに出力するようにしました。

動かすコードのログ出力部分も、その前提を踏まえた形式に変更します。

package xxx.yyy.zzz;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

import xxx.yyy.zzz.impl.Foo;

public class Main {

	private static final Logger logger = LoggerFactory.getLogger(Main.class);
	private static Marker msg = MarkerFactory.getMarker("MESSAGE");
	private static Marker detail = MarkerFactory.getMarker("DETAIL");

	public static void main(String[] args) {

		QQQ foo = new Foo("foo");
		logger.info(msg, "> hello!");
		logger.info(detail, "mogamoga(for developers)");
		foo.execute();
		logger.info(msg, "> bye!");

		// for sample
		logger.debug("Main debug hogehoge");
		logger.warn("Main warn hogehoge");
		logger.error("Main error hogehoge");
	}
}

残りのコード(インタフェースQQQ.javaと、実装クラスFoo.java)は、前回と同じです。

コンソール出力結果。

> hello!
> bye!

ファイル出力結果。

2012-06-19 01:50:46,887  INFO x.y.z.Main - mogamoga(for developers)
2012-06-19 01:50:46,889 DEBUG x.y.z.i.Foo - Foo#execute done.
2012-06-19 01:50:46,889  INFO x.y.z.i.Foo - Foo info hogehoge
2012-06-19 01:50:46,889  WARN x.y.z.i.Foo - Foo warn hogehoge
2012-06-19 01:50:46,889 ERROR x.y.z.i.Foo - Foo error hogehoge
2012-06-19 01:50:46,889 DEBUG x.y.z.Main - Main debug hogehoge
2012-06-19 01:50:46,889  WARN x.y.z.Main - Main warn hogehoge
2012-06-19 01:50:46,889 ERROR x.y.z.Main - Main error hogehoge

やったー。これがやりたかったんダヨー。

expression の要素には、式が書けます。セミコロンで区切って複数の式を書くこともできますし、if文なんかも書けます。(だからcommons-compilerが必要になるんですね)

ほかにも、ドキュメントのサンプルを見ると、エラーオブジェクトに詰め込んだ状態を見て処理を分岐させるような処理もやっています。エラーのステータスコードが404だったら、特定のURIにリクエストを投げたりだとか。ログの設定ファイルに書くには、ちと複雑な処理のような気がしますが、そういう使い方もあるんですね。

logbackすごい。