Log4j2を使ってみる(設定ファイル一切無しで)

大昔に書いたプログラムではLog4j+commmons loggingを使用していたのだが、これまたかなり以前にLog4j2というものが出ているようだ。これを使ってみる。

目標としては、以下だ。

  • 設定ファイルは一切使わない。すべてをプログラム上で制御する。
  • なぜなら、プログラム上で設定を読み込みたいし、プログラム実行途中で設定を変更したいから。

巷にはほぼ設定ファイルを使う例しか無いのだが、彼らは困らないのだろうか?特に、実行途中にTRACE(DEBUG)をいくつかのロガーについてだけONにし、不要になったらOFFにするということが無いのだろうか?はなはだ疑問である。

ライブラリ

Graldeで以下の依存を取得する。

dependencies {
  compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.12.0'
}

基本的な使い方

Log4jに比較するとややこしい。

package foo.bar;

import org.apache.logging.log4j.*;
import org.apache.logging.log4j.core.config.*;
import org.apache.logging.log4j.core.config.builder.api.*;
import org.apache.logging.log4j.core.config.builder.impl.*;

public class Main {

  public static void main(String[]args) {

    // コンフィグビルダを作成する
    ConfigurationBuilder<BuiltConfiguration> configBuilder
      = ConfigurationBuilderFactory.newConfigurationBuilder();

    // コンソールアペンダを作成し、コンフィグに入れる
    String STDOUT = "STDOUT";
    AppenderComponentBuilder consoleBuilder
      = configBuilder.newAppender(STDOUT, "Console");
    LayoutComponentBuilder standard 
      = configBuilder.newLayout("PatternLayout");
    standard.addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable");
    consoleBuilder.add(standard);
    configBuilder.add(consoleBuilder);

    // ルートロガーを作り、コンフィグに入れる
    RootLoggerComponentBuilder rootLogger
      = configBuilder.newRootLogger(Level.TRACE);
    rootLogger.add(configBuilder.newAppenderRef(STDOUT));
    configBuilder.add(rootLogger);

    // ビルドしてコンフィグを初期化
    Configurator.initialize(configBuilder.build());

    // トレースを出してみる。
    LogManager.getLogger(Main.class).trace("this is trace");
  }
}

しかし、この場合コンフィグ以降に作成されたロガーにしか効果が無いのだそうだ。つまり、以下ではうまくいかない。

public class Main {
  // ここでロガーを作成
  static Logger log = LogManager.getLogger(Main.class);

  public static void main(String[]args) {
    // ここでコンフィグ

    // トレースを出してみる。が、出ない。
    log.trace("this is trace");
  }
}

ロガーが作成される以前にコンフィグする

ということで、いかなるロガーが作成される以前にもコンフィグを行わねばならないのだが、いくつか考えられるそうだ。

staticで行う

staticの初期化順序は記述された通りなので、これでもうまく行くようではある。要するにエントリクラスの最初でやればいいわけだ。

public class Main {

  static  {
    initConfig();
  }

  static Logger log = LogManager.getLogger(Main.class);

  public static void main(String[]args) {
    log.trace("this is trace");
  }

  static void initConfig() {
   ... ここでコンフィグ ...

起動引数で指定する

ConfigurationFactoryというもののサブクラスを作り、そこにコンフィグを書き、それをVM引数で指定するのだそうだ。これはパス。

-Dlog4j2.configurationFactory=ConfigurationFactoryクラス名

@Pluginアノテーションを使う

ConfigurationFactoryのサブクラスに@Pluginアノテーションをつけておけば、Log4j2が拾ってくれるのだというが、適当にやってみてもうまく行かなかった。しかしこの方法もパス。

@Plugin(
  name = "CustomConfigurationFactory", 
  category = ConfigurationFactory.CATEGORY)
@Order(50)
public class CustomConfigFactory
  extends ConfigurationFactory {

  // ... rest of implementation
}

実行時にコンフィグを変更する

次に実行中にログレベルを変更したり、アペンダーを追加・削除できるかという問題に行く。

package foo.bar;
....
public class Main {
  ....
  private static Logger log = LogManager.getLogger(Main.class);

    log.trace("this is trace 1");

    Configurator.setAllLevels("foo.bar",  Level.INFO);
    log.trace("this is trace 2");

    Map<String, Level>map = new HashMap<>() {{
      put("foo.bar.Main", Level.TRACE);
    }};
    Configurator.setLevel(map);
    log.trace("this is trace 3");  

    Configurator.setRootLevel(Level.INFO);
    log.trace("this is trace 4");
  }

結果は以下だ。

2019-07-26 09:54:42,846 [main] TRACE: this is trace 1
2019-07-26 09:54:42,850 [main] TRACE: this is trace 3
2019-07-26 09:54:42,851 [main] TRACE: this is trace 4

setAllLevelsの方は”foo.bar”以下すべてのレベルを変更し、setLevelの方はマップのキーに完全一致するロガーのみを変更するらしい。

setRootLevelはルートを変更するようだが、既に何らかを設定されているロガーには影響が無いようだ。どうやってリセットするのだろうか?

実行中のアペンダー追加・削除

次は実行中でのアペンダーの追加・削除だ。できなければ、むしろコンフィグ全体を変更してもよいのだが、最初に見たように無理のような気がする。。。。が、一応例があった。本家の例はあまりにわかりにくくて何をやっているのかわからないのでパスする。

  public static void main(String[]args) {
    log.trace("this is trace 1");

    Configurator.setAllLevels("foo.bar",  Level.INFO);
    log.trace("this is trace 2");

    Map<String, Level>map = new HashMap<>() {{
      put("foo.bar.Main", Level.TRACE);
    }};
    Configurator.setLevel(map);
    log.trace("this is trace 3");  

    Configurator.setRootLevel(Level.INFO);
    log.trace("this is trace 4");


    LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
    FileAppender fa = FileAppender.newBuilder()
        .withName("mylogger")
        .withAppend(false)
        .withFileName("ConsoleOutput.txt")
        .withLayout(PatternLayout.newBuilder().withPattern("%-5p %d  [%t] %C{2} (%F:%L) - %m%n").build())
        .setConfiguration(ctx.getConfiguration())
        .build();

    fa.start();
    ctx.getConfiguration().addAppender(fa);
    ctx.getRootLogger().addAppender(ctx.getConfiguration().getAppender(fa.getName()));
    ctx.updateLoggers();


    log.trace("this is trace 5");

    final Configuration config = ctx.getConfiguration();
    config.getRootLogger().removeAppender("mylogger");
    ctx.updateLoggers();

    log.trace("this is trace 6");

  }

使用したバージョンでは既にDeprecatedになっているメソッドもあるが、ともあれ動くことは動く。削除も機能しているようだ。。。。

Log4j2を使ってみる(設定ファイル一切無しで)、その2に続く。

参考