1. kazurof

    Posted

    kazurof
Changes in title
+Log4j 2でログ出力をテストするサンプルソース
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,181 @@
+Log4j 2でログ出力をテストするサンプルソースです。ちなみにLog4j 1.*系とはAPIが構造もろとも変わっており、色々書き直す必要があります。(`Logger`から`addAppender()`メソッドが無くなっています。)
+
+結論、ここに本家ドキュメントの説明があるのですが、勉強の意味もこめて僕なりにサンプルを書いてみました。
+
+http://logging.apache.org/log4j/2.x/manual/customconfig.html#AppendingToWritersAndOutputStreams
+
+# ソース
+
+## テスト対象のコード
+
+``` SampleClass.java
+package org.example;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class SampleClass {
+ private static final Logger LOGGER = LogManager.getLogger(SampleClass.class);
+
+ void doSomething(String message) {
+ // Java8 & Log4j2として、ラムダを使うことでinfoか否かの判定をLog4j2に実施させる。
+ // みんなラムダで幸せになろうよ。
+ LOGGER.info(() -> "message ->" + message);
+ }
+}
+```
+## テストコード
+
+``` SampleClassTest.java
+package org.example;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.WriterAppender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.junit.Test;
+
+import java.io.StringWriter;
+import java.io.Writer;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class SampleClassTest {
+ static final String TEST_APPENDER_NAME = "fortest";
+
+ @Test
+ public void testDoSomething() throws Exception {
+ StringWriter writer = new StringWriter();
+ //現在のロガーに、writerを持つappenderを追加する。
+ addAppender(writer, TEST_APPENDER_NAME);
+
+ // テスト実行して、writerに流れてきている文字列を検証する。
+ SampleClass sut = new SampleClass();
+ sut.doSomething("nantoka");
+ assertThat(writer.toString(), is("message ->nantoka" + System.lineSeparator()));
+
+ // 追加したappenderを削除する。writerの中身も消す。
+ removeAppender(TEST_APPENDER_NAME);
+ writer.getBuffer().delete(0, writer.getBuffer().length());
+
+ // もう一度テスト実行して、writerに流れてきている文字列を検証する。今度は空である。
+ sut = new SampleClass();
+ sut.doSomething("kantoka");
+ assertTrue(writer.toString().length() == 0);
+ }
+
+ void addAppender(Writer writer, String name) {
+ final LoggerContext context = LoggerContext.getContext(false);
+ final Configuration config = context.getConfiguration();
+ final PatternLayout layout = PatternLayout.createDefaultLayout(config);
+
+ Appender appender = WriterAppender.createAppender(layout, null, writer, name, false, true);
+ appender.start();
+ config.addAppender(appender);
+ updateLoggers(appender, config);
+ }
+
+ private void updateLoggers(final Appender appender, final Configuration config) {
+ for (final LoggerConfig loggerConfig : config.getLoggers().values()) {
+ loggerConfig.addAppender(appender, null, null);
+ }
+ config.getRootLogger().addAppender(appender, null, null);
+ }
+
+ private void removeAppender(String name) {
+ final LoggerContext context = LoggerContext.getContext(false);
+ final Configuration config = context.getConfiguration();
+ for (final LoggerConfig loggerConfig : config.getLoggers().values()) {
+ loggerConfig.removeAppender(name);
+ }
+ config.getRootLogger().removeAppender(name);
+ }
+}
+```
+
+## Log4j 2 設定ファイル
+
+``` log4j2.xml
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="OFF">
+ <appenders>
+ <Console name="console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d %-5p [%t] - %m (%C.java:%L)%n"/>
+ </Console>
+ </appenders>
+
+ <loggers>
+ <Logger name="org.example" level="debug" additivity="false">
+ <appender-ref level="info" ref="console"/>
+ </Logger>
+ <root level="trace">
+ <appender-ref ref="console"/>
+ </root>
+ </loggers>
+</configuration>
+```
+
+## gradle ビルドファイル
+
+``` build.gradle
+apply plugin: 'java'
+
+sourceCompatibility = '1.8'
+targetCompatibility = '1.8'
+
+def defaultEncoding = 'UTF-8'
+
+tasks.withType(AbstractCompile) each { it.options.encoding = defaultEncoding }
+
+test {
+ testLogging.showStandardStreams = true
+}
+
+repositories {
+ jcenter()
+}
+
+dependencies {
+ testCompile 'junit:junit:4.12'
+ compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.5'
+ compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.5'
+}
+```
+
+# 実行結果
+
+```
+C:\home\idea\log4j2>gradle test
+:compileJava
+:processResources
+:classes
+:compileTestJava
+:processTestResources UP-TO-DATE
+:testClasses
+:test
+
+org.example.SampleClassTest > testDoSomething STANDARD_OUT
+ 2016-02-22 01:40:58,092 INFO [Test worker] - message ->nantoka (org.example.SampleClass.java:11)
+ 2016-02-22 01:40:58,097 INFO [Test worker] - message ->kantoka (org.example.SampleClass.java:11)
+
+BUILD SUCCESSFUL
+
+Total time: 14.312 secs
+C:\home\idea\log4j2>
+```
+
+# 所感
+
+Log4j2のドキュメントでは、「プログラムでログ設定をいじるAPIを単純にしました。」とは言うけれど、なんだか関わるクラスが増えているような気がしないでもない。`Appender`の識別を名前ベースでできるようになったのは良いことだと思う。(1.*では、`Appender`のインスタンスそのもので識別だった。)私の習熟度もまだ低いので、writerを追加するためだけだったらもっと簡単に書けるかもしれない。
+
+ちなみに、Loggerをモックする方向も考えたが、ライブラリの依存をなるべく減らしたいことと、Log4j2の機能で実現できることであるのでこちらを採用することにした。
+
+# バージョン番号など
+
+- Java8
+- Log4j 2.5
+- Gradle 2.11