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

2019年11月1日

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

自前のConfigurationなしにログを出力した場合には、DefaultConfigurationというものが使われ、最初に以下のようなERRORが表示され、その後のログはエラーのみがコンソールに表示される。

ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2

Log4j2を使ってみる(設定ファイル一切無しで)、その1に書いたように、コンフィグ作成後に作成したロガーしか有効にはならないため、初期化とロガー作成の順序が問題になったのだが、新たなコンフィグを作らずデフォルトのコンフィグのままで、そこにアペンダを付け加えていくようにはできるだろうか?

前回のWrapperを以下のように変更してみた。

※以下のプログラムは動作しないことがある。以下のエラーが発生する場合があるのだ

java.lang.ClassCastException: class org.apache.logging.log4j.simple.SimpleLoggerContext cannot be cast to class org.apache.logging.log4j.core.LoggerContext (org.apache.logging.log4j.simple.SimpleLoggerContext and org.apache.logging.log4j.core.LoggerContext are in unnamed module of loader 'app')
    at nato.base.clog.CLogConfig.initialize(CLogConfig.java:105)
    at nato.base.webServer.guice.NatoServletContextListener.<clinit>(NatoServletContextListener.java:101)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

「context = (LoggerContext)LogManager.getContext(false);」の部分について、開発環境では「org.apache.logging.log4j.core.LoggerContext」が戻ってくるのに、本番環境では「org.apache.logging.log4j.simple.SimpleLoggerContext」が戻ってくるため、キャストエラーになってしまう。

package foo.bar;


import java.nio.file.*;

import org.apache.logging.log4j.*;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.appender.*;
import org.apache.logging.log4j.core.config.*;
import org.apache.logging.log4j.core.layout.*;
import org.apache.logging.log4j.status.*;

/**
 * Log4jのデフォルトコンフィギュレーションそのままを、プログラムで変更。
 * @author ysugimura
 */
public class Wrapper {

  Configuration configuration;
  LoggerContext context;
  org.apache.logging.log4j.core.Logger ctxRootLogger;
  LoggerConfig configRootLogger;

  public Wrapper(Level level) {

    // Log4j2のDefaultConfigurationの出すWarningを消す
    // https://stackoverflow.com/questions/27994264/log4j2-disable-no-log4j2-configuration-file-found-print-when-configuring-pr
    StatusLogger.getLogger().setLevel(Level.OFF);

    // コンテキストからコンフィギュレーションを得る
    context = (LoggerContext)LogManager.getContext(false);
    configuration = context.getConfiguration();
    // --> org.apache.logging.log4j.core.config.DefaultConfiguration

    // なぜかRootLoggerという名前のものが二種類ある
    ctxRootLogger = context.getRootLogger();
    configRootLogger = configuration.getRootLogger();

    // デフォルトコンフィグではエラーしか出さないので指定レベルに変更
    setAllLevels("", level);    

    // デフォルトコンフィグのデフォルトコンソールアペンダを削除
    for (String name: ctxRootLogger.getAppenders().keySet()) {
      removeAppender(name);
    }
  }

  public void setAllLevels(String parent, Level level) {
    Configurator.setAllLevels(parent,  level);
  }

  public void consoleAppender(String name, String layout) {
    ConsoleAppender appender = ConsoleAppender.newBuilder()
      .setName(name)
      .setLayout(PatternLayout.newBuilder().withPattern(layout).build())
      .build();
    appender.start();
    configuration.addAppender(appender);
    ctxRootLogger.addAppender(
      context.getConfiguration().getAppender(appender.getName())
    );
    context.updateLoggers();    
  }

  public void fileAppender(String name, String layout, Path output) {
    FileAppender appender = FileAppender.newBuilder()
      .setName(name)
      .withAppend(false)
      .withFileName(output.toString())
      .setLayout(PatternLayout.newBuilder().withPattern(layout).build())
      .setConfiguration(context.getConfiguration())
      .build();  
    appender.start();
    configuration.addAppender(appender);
    ctxRootLogger.addAppender(context.getConfiguration().getAppender(appender.getName()));
    context.updateLoggers();
  }

  public void removeAppender(String name) {
    configRootLogger.removeAppender(name);
    context.updateLoggers();    
  }
}

テストは以下だ。仮にWrapperの初期化が遅れても、初期化後ではちゃんと機能するはずだ。

package foo.bar;


import java.nio.file.*;

import org.apache.logging.log4j.*;

public class Main {

  static Wrapper wrapper = new Wrapper(Level.INFO);
  static Logger log = LogManager.getLogger(Main.class);

  public static void main(String[]args) {

    log.info("info 1");
    log.trace("trace 1");

    wrapper.consoleAppender("CONSOLE",  "%d{MM/dd HH:mm} %-5p %c{1} - %m%n");

    log.info("info 2");
    log.trace("trace 2");

    wrapper.setAllLevels("foo.bar", Level.TRACE);

    log.info("info 3");
    log.trace("trace 3");

    wrapper.fileAppender("FILE",  "%d{MM/dd HH:mm} %-5p %c{1} - %m%n", Paths.get("logging.txt"));

    log.info("info 4");
    log.trace("trace 4");

    wrapper.removeAppender("CONSOLE");

    log.info("info 5");
    log.trace("trace 5");

    wrapper.removeAppender("FILE");

    log.info("info 6");
    log.trace("trace 6");
  }
}