この記事は リクルートライフスタイルアドベントカレンダー2017 の5日目です。
ホットペッパービューティーでゆるふわエンジニアをしています。しゃぜです。

はじめに

今回は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記法だと困るケースがありました。具体的にいうと、この仕事の時だったんですが。

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

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

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

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

以下動作サンプルのコードです。githubにもあげておきますね。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にはGroovy Configurationもあるので、そっちでもやりたいことはできたのかも?機会があったら試してみようと思います。
  • ではではー。Merry Xmas !!!