Log4j Transform Maven Plugin

The Transform Plugin is used to postprocess the compiled classes of your project and replace all Log4j 2.x API calls with LogBuilder calls with a statically precomputed location. This allows you to use location information in your logs without incurring in the expensive runtime calls usually used to acquire it.

Why do we need it

Finding the location of a logging call is a very expensive operation (a couple of microseconds).

Running LocationBenchmark on a Ryzen 7 2700U laptop with Java 17 gives the following results:

Logging interface Sync/async logger No. threads Precomputed location Score Error Units

LogBuilder

sync

1

yes

202343,624

±719,875

ops/s

LogBuilder

sync

1

no

68449,813

±5086,148

ops/s

Logger

sync

1

yes

202579,793

±547,961

ops/s

Logger

sync

1

no

100105,246

±13748,554

ops/s

Logger

async

8

yes

726877,012

±38214,575

ops/s

Logger

async

8

no

440245,135

±4849,946

ops/s

The figures show a performance bump of around 5 µs per logging statement when Logger is used and a bump of around 9 µs per logging statement when LogBuilder is used.

By comparison, disabling location information on the same machine gives:

Logging interface Sync/async logger No. threads Precomputed location Score Error Units

LogBuilder

sync

1

yes

234666,556

±19759,779

ops/s

LogBuilder

sync

1

no

212562,315

±3631,670

ops/s

Logger

sync

1

yes

210751,730

±1508,148

ops/s

Logger

sync

1

no

220837,404

±13248,184

ops/s

Logger

async

8

yes

743467,533

±38046,044

ops/s

Logger

async

8

no

776778,635

±38878,794

ops/s

How it works

The working principle is very simple: every call to the Log4j 2.x API like

public void helloLog() {
    logger.info(MarkerManager.getMarker("NET"), "Sending {} bytes of data.", 1000);
}

is rewritten at a bytecode level into an equivalent LogBuilder call:

private static final StackTraceElement[] locations = {
        new StackTraceElement("org.apache.logging.log4j.HelloWorld", "HelloWorld.java", "helloLog", 1234)
};

public void helloLog() {
    logger.atInfo()
          .withLocation(locations[0])
          .withMarker(MarkerManager.getMarker("NET"))
          .log("Sending {} bytes of data.", 1000);
}

In the current implementation locations are stored in classes whose name ends in $$Log4j2$$Cache, so they can not accidentally be used by XML/JSON serializers.

Goals

This plugin consists of a single goal:

log4j-transform:process-classes

is bound to the process-classes phase and weaves your classes to include precomputed location information.

log4j-transform:process-classes

Full name

org.apache.logging.log4j:log4j-transform-maven-plugin:0.2.0:process-classes

Description

Generates static location information of Log4j 2.x API calls in the project classes. The resulting bytecode will not rely on runtime resolution of the location information.

Attributes
  • Requires a Maven project to be executed

  • Requires dependency resolution of artifacts in scope: <code>compile</code>

  • The goal is thread-safe and supports parallel builds

  • Binds by default to the lifecycle phase: process-classes.

Required Parameters

Name Type Description

<sourceDirectory>

File

The directory containing classes to be processed. It defaults to ${project.build.outputDirectory}.

<sourceDirectory>

File

The directory where woven classes will be written. It defaults to ${project.build.outputDirectory}.

Optional Parameters

Name Type Description

<includes>

List<String>

Files to include. If empty all class files will be processed.

<excludes>

List<String>

Files to exclude.

<staleMillis>

int

Sets the granularity in milliseconds of the last modification date for testing if a class file needs weaving. It defaults to 0 and can be configured using the lastModGranularityMs property.

Usage

To use the plugin you need to declare a dependency on log4j-api version 2.20.0 or newer.

Add the following configuration to your POM file:

<plugin>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-transform-maven-plugin</artifactId>
  <version>0.2.0</version>
  <executions>
    <execution>
      <goals>
        <goal>process-classes</goal>
      </goals>
    </execution>
  </executions>
</plugin>