logbackでプログラマブルにLoggerを生成する

この記事は [リクルートライフスタイルアドベントカレンダー2017](http://qiita.com/advent-calendar/2017/recruitlifestyle) の5日目です。
ホットペッパービューティーでゆるふわエンジニアをしています。しゃぜです。

<strong>logbackでプログラマブルにLoggerを生成すると題しまして、動的なLogger生成について書いてみます。</strong>
ま、あんまりドキュメントに無さそうな、logback小ネタです。

# はじめに

通常logbackを使う際は、以下のようなlogback.xmlを用意すると思います。

```logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>
<configuration>

	<appender name="APP"
		class="ch.qos.logback.core.rolling.RollingFileAppender">
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>/var/log/app.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
			<maxHistory>14</maxHistory>
		</rollingPolicy>
		<encoder>
            <pattern>time:%d{yyyy-MMM-dd HH:mm:ss.SSS}  level:%level  marker:%marker thread:%thread  logger:%logger  file:%file  line:%line  message:%msg%n</pattern>
			<charset>UTF-8</charset>
		</encoder>
	</appender>

   	<root level="INFO">
		<appender-ref ref="APP" />
	</root>

</configuration>
```

# やりたいこと

ところが、このXML記法だと困るケースがありました。具体的にいうと、[この仕事](https://engineer.recruit-lifestyle.co.jp/techblog/2016-12-26-kcl-application/)の時だったんですが。

やりたかったこととしては、<strong>ログファイルのPATHを動的に設定したい</strong>です。どういうことかというと...。

* 条件
    * host1から転送されてきたログは、`/tmp/host1/app-yyyyMMdd.log`とする。
    * hostNから転送されてきたログは、`/tmp/hostN/app-yyyyMMdd.log`とする。
    * hostは可変である。


というものです。まぁ、転送されてくるホストの増減に合わせて、xmlをメンテナンスするとかやりたくないです。

いろんなやり方があると思うのですが（logbackを使わないも含めて）、今回は<strong>logbackでプログラマブルにLoggerの生成する</strong>やりかたでやってみました。

以下動作サンプルのコードです。githubにもあげておきますね。[https://github.com/shase/logback-dynamic-logger-sample](https://github.com/shase/logback-dynamic-logger-sample)

```Main.java
import java.util.stream.Stream;
import ch.qos.logback.classic.Logger;

public class Main {
	
	public static void main(String...args) {
		// ここは外部からhost名が入ってくるのをイメージしてみてください。
		String[] path = {"/tmp/host1/app-%d{yyyyMMdd}.log","/tmp/host2/app-%d{yyyyMMdd}.log","/tmp/host3/app-%d{yyyyMMdd}.log"};
	
		Stream.of(path)
			.forEach(p -> {
				Logger logger = new SimpleLoggerFactory().getLogger("sample", p);
				logger.info("dynamic path");
			});
	}

}
```

```SimpleLoggerFactory.java
import java.nio.charset.StandardCharsets;

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;

public class SimpleLoggerFactory {
	public Logger getLogger(String loggerName, String path) {

		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();

		PatternLayoutEncoder ple = new PatternLayoutEncoder();
		ple.setPattern("%msg%n");
		ple.setContext(lc);
		ple.setCharset(StandardCharsets.UTF_8);
		ple.start();

		RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<>();
		TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();
		rollingPolicy.setFileNamePattern(path);
		rollingPolicy.setMaxHistory(14);
		rollingPolicy.setParent(fileAppender);
		rollingPolicy.setContext(lc);
		rollingPolicy.start();

		fileAppender.setAppend(true);
		fileAppender.setEncoder(ple);
		fileAppender.setRollingPolicy(rollingPolicy);
		fileAppender.setContext(lc);
		fileAppender.start();

		Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
		logger.addAppender(fileAppender);
		logger.setAdditive(false);

		return logger;
	}
}
```

* これによって、出力先のpathを動的にすることができました。
* logbackを普段使っている人が見ればわかると思いますが、普段xmlで定義していることをそのまま書いているだけですね。
* この例ではxmlは不要です。

# おわりに
* 実際に使う際は、毎回同じloggerを生成する必要はないので（初回生成だけでよいので）、何かしらの手段でsingletonにするとよいでしょう。
* これを書いた後に気がついたのですが、logbackには<strong>Groovy Configuration</strong>もあるので、そっちでもやりたいことはできたのかも？機会があったら試してみようと思います。
* ではではー。Merry Xmas !!!