blob: cbb977288acad0070473de884abb88aa2ac20e88 [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
= 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 https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/LogBuilder.html[`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 xref:../log4j-transform-perf/src/main/java/org/apache/logging/log4j/transform/perf/LocationBenchmark.java[`LocationBenchmark`] on a Ryzen 7 2700U laptop with Java 17 gives the following results:
[cols="1,1,1,1,1,1,1"]
|===
|Logging interface|Sync/async logger|No. threads|Precomputed location|Score|Error|Units
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/LogBuilder.html[`LogBuilder`]
|sync
|1
|yes
|202343,624
719,875
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/LogBuilder.html[`LogBuilder`]
|sync
|1
|no
|68449,813
5086,148
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
|sync
|1
|yes
|202579,793
547,961
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
|sync
|1
|no
|100105,246
13748,554
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
|async
|8
|yes
|726877,012
38214,575
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
|async
|8
|no
|440245,135
4849,946
|ops/s
|===
The figures show a performance bump of around 5 µs per logging statement when
https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
is used and a bump of around 9 µs per logging statement when
https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/LogBuilder.html[`LogBuilder`]
is used.
By comparison, *disabling* location information on the same machine gives:
[cols="1,1,1,1,1,1,1"]
|===
|Logging interface|Sync/async logger|No. threads|Precomputed location|Score|Error|Units
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/LogBuilder.html[`LogBuilder`]
|sync
|1
|yes
|234666,556
19759,779
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/LogBuilder.html[`LogBuilder`]
|sync
|1
|no
|212562,315
3631,670
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
|sync
|1
|yes
|210751,730
1508,148
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
|sync
|1
|no
|220837,404
13248,184
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`]
|async
|8
|yes
|743467,533
38046,044
|ops/s
|https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`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
[source,java]
----
public void helloLog() {
logger.info(MarkerManager.getMarker("NET"), "Sending {} bytes of data.", 1000);
}
----
is rewritten at a bytecode level into an equivalent `LogBuilder` call:
[source,java]
----
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:
xref:src/doc/process-classes-mojo.adoc[`log4j-transform:process-classes`]::
is bound to the process-classes phase and weaves your classes to include precomputed location information.
== Usage
To use the plugin you need to declare a dependency on https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-api/2.20.0[`log4j-api`] version 2.20.0 or newer.
Add the following configuration to your POM file:
[source,xml]
----
<project>
[...]
<build>
[...]
<plugins>
<plugin>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-transform-maven-plugin</artifactId>
<version>0.1.0-SNAPSHOT</version>
<executions>
<execution>
<goals>
<goal>process-classes</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
[...]
</build>
[...]
</project>
----