1. 概要

Log4j 2は、アペンダーやレイアウトなどのプラグインを使用して、ログをフォーマットおよび出力します。 これらはコアプラグインと呼ばれ、Log4j2には多くのオプションが用意されています。

ただし、場合によっては、既存のプラグインを拡張したり、カスタムプラグインを作成したりする必要もあります。

このチュートリアルでは、Log4j2拡張メカニズムを使用してカスタムプラグインを実装します。

2. Log4j2プラグインの拡張

Log4j 2のプラグインは、大きく5つのカテゴリに分類されます。

  1. コアプラグイン
  2. コンバーター
  3. 主要プロバイダー
  4. ルックアップ
  5. タイプコンバーター

Log4j 2では、共通のメカニズムを使用して、上記のすべてのカテゴリにカスタムプラグインを実装できます。 さらに、同じアプローチで既存のプラグインを拡張することもできます。

Log4j 1.xでは、既存のプラグインを拡張する唯一の方法は、その実装クラスをオーバーライドすることです。 一方、Log4j 2では、クラスに@Pluginアノテーションを付けることで、既存のプラグインを簡単に拡張できます。

次のセクションでは、これらのカテゴリのいくつかにカスタムプラグインを実装します。

3. コアプラグイン

3.1. カスタムコアプラグインの実装

Appenders、Layouts、Filtersなどの主要な要素は、Log4j 2 ではコアプラグインとして知られています。このようなプラグインにはさまざまなリストがありますが、場合によっては、カスタムコアプラグインを実装する必要があります。 たとえば、ログレコードのみをメモリ内のListに書き込むListAppenderについて考えてみます。

@Plugin(name = "ListAppender", 
  category = Core.CATEGORY_NAME, 
  elementType = Appender.ELEMENT_TYPE)
public class ListAppender extends AbstractAppender {

    private List<LogEvent> logList;

    protected ListAppender(String name, Filter filter) {
        super(name, filter, null);
        logList = Collections.synchronizedList(new ArrayList<>());
    }

    @PluginFactory
    public static ListAppender createAppender(
      @PluginAttribute("name") String name, @PluginElement("Filter") final Filter filter) {
        return new ListAppender(name, filter);
    }

    @Override
    public void append(LogEvent event) {
        if (event.getLevel().isLessSpecificThan(Level.WARN)) {
            error("Unable to log less than WARN level.");
            return;
        }
        logList.add(event);
    }
}

プラグインに名前を付けることができる@Pluginでクラスに注釈を付けました。 また、パラメータには注釈が付けられています @PluginAttribute。 フィルタやレイアウトなどのネストされた要素は、として渡されます @PluginElement。 これで、同じ名前を使用して構成でこのプラグインを参照できます。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xi="http://www.w3.org/2001/XInclude"
    packages="com.baeldung" status="WARN">
    <Appenders>
        <ListAppender name="ListAppender">
            <BurstFilter level="INFO" rate="16" maxBurst="100"/>
        </MapAppender>
    </Appenders>
    <Loggers
        <Root level="DEBUG">
            <AppenderRef ref="ConsoleAppender" />
            <AppenderRef ref="ListAppender" />
        </Root>
    </Loggers>
</Configuration>

3.2. プラグインビルダー

前のセクションの例はかなり単純で、単一のパラメーターのみを受け入れます名前。 一般的に、アペンダーのようなコアプラグインははるかに複雑で、通常、いくつかの構成可能なパラメーターを受け入れます。

たとえば、ログを Kafka:に書き込むアペンダーについて考えてみます。

<Kafka2 name="KafkaLogger" ip ="127.0.0.1" port="9010" topic="log" partition="p-1">
    <PatternLayout pattern="%pid%style{%message}{red}%n" />
</Kafka2>

このようなアペンダーを実装するために、Log4j2はビルダーパターンに基づくプラグインビルダーの実装を提供します。

@Plugin(name = "Kafka2", category = Core.CATEGORY_NAME)
public class KafkaAppender extends AbstractAppender {

    public static class Builder implements org.apache.logging.log4j.core.util.Builder<KafkaAppender> {

        @PluginBuilderAttribute("name")
        @Required
        private String name;

        @PluginBuilderAttribute("ip")
        private String ipAddress;

        // ... additional properties

        // ... getters and setters

        @Override
        public KafkaAppender build() {
            return new KafkaAppender(
              getName(), getFilter(), getLayout(), true, new KafkaBroker(ipAddress, port, topic, partition));
        }
    }

    private KafkaBroker broker;

    private KafkaAppender(String name, Filter filter, Layout<? extends Serializable> layout, 
      boolean ignoreExceptions, KafkaBroker broker) {
        super(name, filter, layout, ignoreExceptions);
        this.broker = broker;
    }

    @Override
    public void append(LogEvent event) {
        connectAndSendToKafka(broker, event);
    }
}

要するに、私たちはビルダークラスとパラメータに注釈を付けます @PluginBuilderAttribute。 このため、 KafkaAppender 上記の構成からKafka接続パラメーターを受け入れます。

3.3. 既存のプラグインの拡張

Log4j2で既存のコアプラグインを拡張することもできますこれは、プラグインに既存のプラグインと同じ名前を付けることで実現できます。 たとえば、 RollingFileAppender:を拡張する場合

@Plugin(name = "RollingFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class RollingFileAppender extends AbstractAppender {

    public RollingFileAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
        super(name, filter, layout);
    }
    @Override
    public void append(LogEvent event) {
    }
}

特に、同じ名前の2つのアペンダーがあります。 このようなシナリオでは、Log4j2は最初に検出されたアペンダーを使用します。 プラグインの検出については、後のセクションで詳しく説明します。

Log4j 2は、同じ名前の複数のプラグインを推奨しないことに注意してください。 代わりにカスタムプラグインを実装し、それをロギング構成で使用することをお勧めします。

4. コンバータプラグイン

レイアウトはLog4j2の強力なプラグインです 。 これにより、ログの出力構造を定義できます。 たとえば、JsonLayoutを使用してログをJSON形式で書き込むことができます。

別のそのようなプラグインは PatternLayout。 場合によっては、アプリケーションは、スレッドID、スレッド名、タイムスタンプなどの情報を各ログステートメントで公開する必要があります。 PatternLayout プラグインを使用すると、構成内の変換パターン文字列を介してこのような詳細を埋め込むことができます。

<Configuration status="debug" name="baeldung" packages="">
    <Appenders>
        <Console name="stdout" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
        </Console>
    </Appenders>
</Configuration>

ここで、%dは変換パターンです。 Log4j 2は、この%dパターンを、変換パターンを理解し、フォーマットされた日付またはタイムスタンプに置き換えるDatePatternConverterを介して変換します。

ここで、Dockerコンテナー内で実行されているアプリケーションが、すべてのログステートメントでコンテナー名を出力したいとします。 これを行うには、 DockerPatterConverter を実装し、上記の構成を変更して変換文字列を含めます。

@Plugin(name = "DockerPatternConverter", category = PatternConverter.CATEGORY)
@ConverterKeys({"docker", "container"})
public class DockerPatternConverter extends LogEventPatternConverter {

    private DockerPatternConverter(String[] options) {
        super("Docker", "docker");
    }

    public static DockerPatternConverter newInstance(String[] options) {
        return new DockerPatternConverter(options);
    }

    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
        toAppendTo.append(dockerContainer());
    }

    private String dockerContainer() {
        return "container-1";
    }
}

そこで、日付パターンに似たカスタムDockerPatternConverterを実装しました。 変換パターンがDockerコンテナの名前に置き換えられます。

このプラグインは、以前に実装したコアプラグインに似ています。 特に、前回のプラグインとは異なるアノテーションが1つだけあります。 @ConverterKeysアノテーションは、このプラグインの変換パターンを受け入れます。

その結果、このプラグインは %dockerまたは%containerパターン文字列をアプリケーションが実行されているコンテナー名に変換します。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xi="http://www.w3.org/2001/XInclude" packages="com.baeldung" status="WARN">
    <Appenders>
        <xi:include href="log4j2-includes/console-appender_pattern-layout_colored.xml" />
        <Console name="DockerConsoleLogger" target="SYSTEM_OUT">
            <PatternLayout pattern="%pid %docker %container" />
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="com.baeldung.logging.log4j2.plugins" level="INFO">
            <AppenderRef ref="DockerConsoleLogger" />
        </Logger>
    </Loggers>
</Configuration>

5. ルックアッププラグイン

ルックアッププラグインは、Log4j2構成ファイルに動的な値を追加するために使用されます。 これにより、アプリケーションは実行時の値を構成ファイルの一部のプロパティに埋め込むことができます。 値は、ファイルシステム、データベースなどのさまざまなソースでのキーベースのルックアップによって追加されます。

そのようなプラグインの1つは、日付パターンをアプリケーションの現在のシステム日付に置き換えることができるDateLookupPluginです

<RollingFile name="Rolling-File" fileName="${filename}" 
  filePattern="target/rolling1/test1-$${date:MM-dd-yyyy}.%i.log.gz">
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <SizeBasedTriggeringPolicy size="500" />
</RollingFile>

このサンプル構成ファイルでは、 RollingFileAppender 日付ルックアップを使用し、出力はMM-dd-yyyy形式になります。 その結果、Log4j2はログを日付サフィックス付きの出力ファイルに書き込みます。

他のプラグインと同様に、Log4j2はlookupsの多くのソースを提供します。 さらに、新しいソースが必要な場合は、カスタムルックアップを簡単に実装できます。

@Plugin(name = "kafka", category = StrLookup.CATEGORY)
public class KafkaLookup implements StrLookup {

    @Override
    public String lookup(String key) {
        return getFromKafka(key);
    }

    @Override
    public String lookup(LogEvent event, String key) {
        return getFromKafka(key);
    }

    private String getFromKafka(String topicName) {
        return "topic1-p1";
    }
}

したがって、 KafkaLookup は、Kafkaトピックを照会することによって値を解決します。 次に、構成からトピック名を渡します。

<RollingFile name="Rolling-File" fileName="${filename}" 
  filePattern="target/rolling1/test1-$${kafka:topic-1}.%i.log.gz">
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <SizeBasedTriggeringPolicy size="500" />
</RollingFile>

前の例のdate lookupを、topic-1を照会するKafkaルックアップに置き換えました。

Log4j 2はルックアッププラグインのデフォルトコンストラクターのみを呼び出すため、以前のプラグインのように@PluginFactoryを実装しませんでした。

6. プラグインの発見

最後に、Log4j2がアプリケーション内のプラグインを検出する方法を理解しましょう。 上記の例で見たように、各プラグインに一意の名前を付けました。 この名前はキーとして機能し、Log4j2はプラグインクラスに解決します。

Log4j2がプラグインクラスを解決するためにルックアップを実行する特定の順序があります。

  1. log4j2-coreライブラリのシリアル化されたプラグインリストファイル。 具体的には、 Log4j2Plugins.dat がこのjar内にパッケージ化されており、デフォルトのLog4j2プラグインが一覧表示されます。
  2. OSGiバンドルからの同様のLog4j2Plugins.datファイル
  3. log4j.plugin.packagesシステムプロパティのコンマ区切りのパッケージリスト
  4. プログラムによるLog4j2構成では、 PluginManager.addPackages()メソッドを呼び出して、パッケージ名のリストを追加できます。
  5. パッケージのコンマ区切りリストは、Log4j2構成ファイルに追加できます。

前提条件として、Log4j2が@Plugin アノテーションで指定された名前でプラグインを解決できるようにするには、アノテーション処理を有効にする必要があります。

Log4j 2は名前を使用してプラグインを検索するため、上記の順序が重要になります。 たとえば、同じ名前のプラグインが2つある場合、Log4j 2は最初に解決されるプラグインを検出します。したがって、Log4j 2で既存のプラグインを拡張する必要がある場合は、プラグインを別のjarにパッケージ化する必要があります。 log4j2-core.jar。の前に配置します

7. 結論

この記事では、Log4j2のプラグインの幅広いカテゴリについて説明しました。既存のプラグインの完全なリストがありますが、一部のユースケースではカスタムプラグインを実装する必要がある場合があることを説明しました。

後で、いくつかの便利なプラグインのカスタム実装を調べました。 さらに、Log4j 2を使用してこれらのプラグインに名前を付け、その後、構成ファイルでこのプラグイン名を使用する方法を確認しました。 最後に、Log4j2がこの名前に基づいてプラグインを解決する方法について説明しました。

いつものように、すべての例はGitHubから入手できます。