You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2023/01/10 19:48:30 UTC

[logging-log4j-transform] branch main updated: [LOG4J2-3638] Pre-compute location information

This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/logging-log4j-transform.git


The following commit(s) were added to refs/heads/main by this push:
     new 70e0328  [LOG4J2-3638] Pre-compute location information
70e0328 is described below

commit 70e0328139cd3c72f04b0a75cb14daed7019bc70
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Sun Jan 8 21:05:25 2023 +0100

    [LOG4J2-3638] Pre-compute location information
    
    Transforms `Logger` method calls to:
    
     * use `LogBuilder` instead,
     * use a pre-computed location value.
    
    This closes #1.
---
 log4j-transform-maven-plugin/pom.xml               |  77 ++
 .../src/it/location/pom.xml                        |  64 ++
 .../logging/log4j/maven/it/location/Main.java      |  35 +
 .../apache/logging/log4j/maven/LocationMojo.java   | 139 +++
 .../maven/scan/ClassFileInclusionScanner.java      |  32 +
 .../log4j/maven/scan/SimpleInclusionScanner.java   |  97 +++
 log4j-transform-parent/pom.xml                     |  82 +-
 log4j-weaver/pom.xml                               |  96 +++
 .../log4j/weaver/ClassConversionHandler.java       |  37 +
 .../org/apache/logging/log4j/weaver/Constants.java |  75 ++
 .../logging/log4j/weaver/ConversionException.java  |  30 +
 .../log4j/weaver/LocationCacheGenerator.java       | 281 ++++++
 .../log4j/weaver/LocationClassConverter.java       |  47 +
 .../logging/log4j/weaver/LocationClassVisitor.java |  88 ++
 .../log4j/weaver/LocationMethodVisitor.java        | 152 ++++
 .../logging/log4j/weaver/SupplierLambdaType.java   |  70 ++
 .../weaver/log4j2/LogBuilderConversionHandler.java |  42 +
 .../weaver/log4j2/LoggerConversionHandler.java     | 343 ++++++++
 .../weaver/AbstractConversionHandlerTest.java      |  69 ++
 .../logging/log4j/weaver/NoLoggingExample.java     |  24 +
 .../apache/logging/log4j/weaver/NoLoggingTest.java |  42 +
 .../log4j2/LogBuilderConversionHandlerExample.java |  45 +
 .../log4j2/LogBuilderConversionHandlerTest.java    |  36 +
 .../log4j/weaver/log4j2/LogBuilderMock.java        |  44 +
 .../log4j2/LoggerConversionHandlerExample.java     | 943 +++++++++++++++++++++
 .../weaver/log4j2/LoggerConversionHandlerTest.java |  58 ++
 log4j-weaver/src/test/resources/log4j2-test.xml    |  27 +
 pom.xml                                            |  22 +-
 ...638_Provide_Maven_plugin_to_inline_location.xml |   7 +
 29 files changed, 3094 insertions(+), 10 deletions(-)

diff --git a/log4j-transform-maven-plugin/pom.xml b/log4j-transform-maven-plugin/pom.xml
new file mode 100644
index 0000000..a1e076c
--- /dev/null
+++ b/log4j-transform-maven-plugin/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.logging.log4j</groupId>
+    <artifactId>log4j-transform-parent</artifactId>
+    <version>${revision}</version>
+    <relativePath>../log4j-transform-parent</relativePath>
+  </parent>
+  <artifactId>log4j-transform-maven-plugin</artifactId>
+  <packaging>maven-plugin</packaging>
+  <name>Apache Log4j Transform Maven plugin</name>
+  <description>The Apache Log4j Transform Maven plugin</description>
+  <properties>
+    <maven.version>3.8.6</maven.version>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-weaver</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-utils</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugin-tools</groupId>
+      <artifactId>maven-plugin-annotations</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-plugin-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>default-jar</id>
+            <configuration>
+              <archive combine.self="override"/>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/log4j-transform-maven-plugin/src/it/location/pom.xml b/log4j-transform-maven-plugin/src/it/location/pom.xml
new file mode 100644
index 0000000..acbda6d
--- /dev/null
+++ b/log4j-transform-maven-plugin/src/it/location/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.logging</groupId>
+    <artifactId>logging-parent</artifactId>
+    <version>7</version>
+    <relativePath/>
+  </parent>
+  <groupId>org.apache.logging.log4j.maven.it</groupId>
+  <artifactId>log4j-maven-it-location</artifactId>
+  <version>3.0.0-SNAPSHOT</version>
+  <name>Apache Log4j Maven plugin test</name>
+  <description>The Apache Log4j Maven plugin test</description>
+  <packaging>jar</packaging>
+  <properties>
+    <log4jParentDir>${basedir}/../../../..</log4jParentDir>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+      <version>${project.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>generate-location</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/log4j-transform-maven-plugin/src/it/location/src/main/java/org/apache/logging/log4j/maven/it/location/Main.java b/log4j-transform-maven-plugin/src/it/location/src/main/java/org/apache/logging/log4j/maven/it/location/Main.java
new file mode 100644
index 0000000..e535c67
--- /dev/null
+++ b/log4j-transform-maven-plugin/src/it/location/src/main/java/org/apache/logging/log4j/maven/it/location/Main.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.maven.it.location;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class Main implements Runnable {
+
+    private static final Logger logger = LogManager.getLogger();
+
+    public static void main(String[] args) {
+        logger.info("Application starts");
+        new Main().run();
+        logger.info("Application stops");
+    }
+
+    public void run() {
+        logger.info("Another log message");
+    }
+}
diff --git a/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/LocationMojo.java b/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/LocationMojo.java
new file mode 100644
index 0000000..b7acb51
--- /dev/null
+++ b/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/LocationMojo.java
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.maven;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.logging.log4j.maven.scan.ClassFileInclusionScanner;
+import org.apache.logging.log4j.maven.scan.SimpleInclusionScanner;
+import org.apache.logging.log4j.weaver.LocationCacheGenerator;
+import org.apache.logging.log4j.weaver.LocationClassConverter;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+
+/**
+ * Generates location information for use with Log4j2.
+ */
+@Mojo(name = "generate-location", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true,
+        requiresDependencyResolution = ResolutionScope.COMPILE)
+public class LocationMojo extends AbstractMojo {
+
+    /**
+     * The directory containing class files to process.
+     */
+    @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
+    private File sourceDirectory;
+
+    /**
+     * The directory containing processed files.
+     */
+    @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
+    private File outputDirectory;
+
+    /**
+     * Sets the granularity in milliseconds of the last modification date for
+     * testing whether a class file needs weaving.
+     */
+    @Parameter(property = "lastModGranularityMs", defaultValue = "0")
+    private int staleMillis;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        final Path sourceDirectory = this.sourceDirectory.toPath();
+        final Path outputDirectory = this.outputDirectory.toPath();
+        final LocationCacheGenerator locationCache = new LocationCacheGenerator();
+        final LocationClassConverter converter = new LocationClassConverter();
+
+        try {
+            final Set<Path> staleClassFiles = getClassFileInclusionScanner().getIncludedClassFiles(sourceDirectory,
+                    outputDirectory);
+            staleClassFiles.stream()
+                    .collect(Collectors.groupingBy(LocationCacheGenerator::getCacheClassFile))
+                    .values()
+                    .parallelStream()
+                    .forEach(p -> convertClassfiles(p, converter, locationCache));
+
+            locationCache.generateClasses().forEach(this::saveClassFile);
+        } catch (WrappedIOException e) {
+            throw new MojoExecutionException("An I/O error occurred.", e.getCause());
+        }
+    }
+
+    private void convertClassfiles(List<Path> classFiles, LocationClassConverter converter,
+            LocationCacheGenerator locationCache) {
+        final Path sourceDirectory = this.sourceDirectory.toPath();
+        classFiles.sort(Path::compareTo);
+        final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        try {
+            for (final Path classFile : classFiles) {
+                buf.reset();
+                try (final InputStream src = Files.newInputStream(sourceDirectory.resolve(classFile))) {
+                    converter.convert(src, buf, locationCache);
+                }
+                saveClassFile(classFile, buf.toByteArray());
+            }
+        } catch (IOException e) {
+            throw new WrappedIOException(e);
+        }
+    }
+
+    private void saveClassFile(String internalClassName, byte[] data) {
+        try {
+            final Path outputDirectory = this.outputDirectory.toPath();
+            Files.write(outputDirectory.resolve(internalClassName + ".class"), data);
+        } catch (IOException e) {
+            throw new WrappedIOException(e);
+        }
+    }
+
+    private void saveClassFile(Path dest, byte[] data) {
+        try {
+            final Path outputDirectory = this.outputDirectory.toPath();
+            Files.write(outputDirectory.resolve(dest), data);
+        } catch (IOException e) {
+            throw new WrappedIOException(e);
+        }
+    }
+
+    protected ClassFileInclusionScanner getClassFileInclusionScanner() {
+        return new SimpleInclusionScanner(staleMillis, getLog());
+    }
+
+    private static class WrappedIOException extends RuntimeException {
+
+        private static final long serialVersionUID = 4290527889488735839L;
+
+        private WrappedIOException(IOException cause) {
+            super(cause);
+        }
+
+    }
+}
diff --git a/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/scan/ClassFileInclusionScanner.java b/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/scan/ClassFileInclusionScanner.java
new file mode 100644
index 0000000..fbefbc5
--- /dev/null
+++ b/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/scan/ClassFileInclusionScanner.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.maven.scan;
+
+import java.nio.file.Path;
+import java.util.Set;
+
+public interface ClassFileInclusionScanner {
+
+    /**
+     * Finds class files matching a specific condition.
+     *
+     * @param sourceDir path to the folder where to search files
+     * @param targetDir an auxiliary folder
+     * @return a set of relative paths to file in {@code sourceDir}
+     */
+    Set<Path> getIncludedClassFiles(Path sourceDir, Path targetDir);
+}
diff --git a/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/scan/SimpleInclusionScanner.java b/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/scan/SimpleInclusionScanner.java
new file mode 100644
index 0000000..48ea78e
--- /dev/null
+++ b/log4j-transform-maven-plugin/src/main/java/org/apache/logging/log4j/maven/scan/SimpleInclusionScanner.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.maven.scan;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.weaver.Constants;
+import org.apache.logging.log4j.weaver.LocationCacheGenerator;
+
+import org.apache.maven.plugin.logging.Log;
+import org.codehaus.plexus.util.DirectoryScanner;
+
+public class SimpleInclusionScanner implements ClassFileInclusionScanner {
+
+    private static final String DEFAULT_INCLUSION_PATTERN = "**/*.class";
+    private static final String DEFAULT_EXCLUSION_PATTERN = "**/*" + Constants.LOCATION_CACHE_SUFFIX + ".class";
+    private static final String[] EMPTY_ARRAY = new String[0];
+
+    private final long lastUpdatedWithinMsecs;
+    private final Set<String> sourceIncludes;
+    private final Set<String> sourceExcludes;
+    private final Log log;
+
+    public SimpleInclusionScanner(long lastUpdateWithinMsecs, Log log) {
+        this(lastUpdateWithinMsecs, Collections.singleton(DEFAULT_INCLUSION_PATTERN),
+                Collections.singleton(DEFAULT_EXCLUSION_PATTERN), log);
+    }
+
+    public SimpleInclusionScanner(long lastUpdateWithinMsecs, Set<String> sourceIncludes, Set<String> sourceExcludes,
+            Log log) {
+        this.lastUpdatedWithinMsecs = lastUpdateWithinMsecs;
+        this.sourceIncludes = new HashSet<>(sourceIncludes);
+        this.sourceExcludes = new HashSet<>(sourceExcludes);
+        this.log = log;
+    }
+
+    @Override
+    public Set<Path> getIncludedClassFiles(Path sourceDir, Path targetDir) {
+        final Set<Path> potentialSources = scanForSources(sourceDir, sourceIncludes, sourceExcludes);
+
+        return potentialSources.stream().filter(source -> isLocationCacheStale(sourceDir, targetDir, source))
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * @return a set of relative paths to class files
+     */
+    private static Set<Path> scanForSources(Path sourceDir, Set<String> sourceIncludes, Set<String> sourceExcludes) {
+        final DirectoryScanner scanner = new DirectoryScanner();
+        scanner.setBasedir(sourceDir.toFile());
+        scanner.setIncludes(sourceIncludes.toArray(EMPTY_ARRAY));
+        scanner.setExcludes(sourceExcludes.toArray(EMPTY_ARRAY));
+        scanner.scan();
+
+        return Stream.of(scanner.getIncludedFiles())
+                .map(sourceDir::resolve)
+                .collect(Collectors.toSet());
+    }
+
+    private boolean isLocationCacheStale(Path sourceDir, Path targetDir, Path source) {
+        try {
+            final Path target = targetDir.resolve(LocationCacheGenerator.getCacheClassFile(source));
+            if (!Files.exists(target)) {
+                return true;
+            }
+
+            final FileTime sourceModifiedTime = Files.getLastModifiedTime(sourceDir.resolve(source));
+            final FileTime targetModifiedTime = Files.getLastModifiedTime(target);
+            return targetModifiedTime.toMillis() - sourceModifiedTime.toMillis() > lastUpdatedWithinMsecs;
+        } catch (IOException e) {
+            log.warn("Unable to open file: " + source, e);
+        }
+        return false;
+    }
+}
diff --git a/log4j-transform-parent/pom.xml b/log4j-transform-parent/pom.xml
index 16e5c95..50d984d 100644
--- a/log4j-transform-parent/pom.xml
+++ b/log4j-transform-parent/pom.xml
@@ -24,38 +24,94 @@
     <version>${revision}</version>
   </parent>
   <artifactId>log4j-transform-parent</artifactId>
-  <version>${revision}</version>
   <packaging>pom</packaging>
   <properties>
     <!-- project properties -->
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-    <!-- `minimalJavaBuildVersion` is employed by `org.apache:apache`, which parents `org.apache.logging:logging-parent`, which parents `org.apache.logging.log4j.tools:log4j-tools-bom`, which parents us.
-         `minimalJavaBuildVersion` is used for enforcing the compiler version.
-         We will use `java.version` to enforce the target JVM byte code, which is 8. -->
-    <minimalJavaBuildVersion>[11,12)</minimalJavaBuildVersion>
-    <java.version>8</java.version>
     <!-- `project.build.outputTimestamp` is required for reproducible builds: https://maven.apache.org/guides/mini/guide-reproducible-builds.html -->
     <project.build.outputTimestamp>0</project.build.outputTimestamp>
     <!-- disable `maven-site-plugin`-->
     <maven.site.skip>true</maven.site.skip>
     <maven.site.deploy.skip>true</maven.site.deploy.skip>
     <!-- dependency versions -->
+    <asm.version>9.4</asm.version>
+    <assertj.version>3.23.1</assertj.version>
+    <commons-lang.version>3.12.0</commons-lang.version>
+    <commons-logging.version>1.2</commons-logging.version>
     <freemarker.version>2.3.31</freemarker.version>
+    <junit.version>5.9.1</junit.version>
+    <log4j2.version>2.19.1-SNAPSHOT</log4j2.version>
+    <maven.version>3.6.0</maven.version>
+    <plexus-utils.version>3.1.0</plexus-utils.version>
+    <slf4j.version>2.0.6</slf4j.version>
     <spotbugs.version>4.7.3</spotbugs.version>
     <!-- plugin versions -->
+    <bundle-plugin.version>5.1.8</bundle-plugin.version>
     <error-prone.version>2.16</error-prone.version>
     <findsecbugs-plugin.version>1.12.0</findsecbugs-plugin.version>
     <spotbugs-maven-plugin.version>${spotbugs.version}.0</spotbugs-maven-plugin.version>
+    <surefire.version>3.0.0-M7</surefire.version>
   </properties>
   <dependencyManagement>
     <dependencies>
+      <dependency>
+        <groupId>org.ow2.asm</groupId>
+        <artifactId>asm-bom</artifactId>
+        <version>${asm.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit</groupId>
+        <artifactId>junit-bom</artifactId>
+        <version>${junit.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-bom</artifactId>
+        <version>${log4j2.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.assertj</groupId>
+        <artifactId>assertj-core</artifactId>
+        <version>${assertj.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-lang3</artifactId>
+        <version>${commons-lang.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>commons-logging</groupId>
+        <artifactId>commons-logging</artifactId>
+        <version>${commons-logging.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.freemarker</groupId>
         <artifactId>freemarker</artifactId>
         <version>${freemarker.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.maven</groupId>
+        <artifactId>maven-plugin-api</artifactId>
+        <version>${maven.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.codehaus.plexus</groupId>
+        <artifactId>plexus-utils</artifactId>
+        <version>${plexus-utils.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>${slf4j.version}</version>
+      </dependency>
       <dependency>
         <groupId>com.github.spotbugs</groupId>
         <artifactId>spotbugs-annotations</artifactId>
@@ -66,6 +122,11 @@
   <build>
     <pluginManagement>
       <plugins>
+        <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <version>${bundle-plugin.version}</version>
+        </plugin>
         <plugin>
           <groupId>com.github.spotbugs</groupId>
           <artifactId>spotbugs-maven-plugin</artifactId>
@@ -156,6 +217,15 @@
           </execution>
         </executions>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <jdkToolchain>
+            <version>${java.version}</version>
+          </jdkToolchain>
+        </configuration>
+      </plugin>
       <!-- Configuration here must match the one in `.editorconfig`! -->
       <plugin>
         <groupId>com.github.spotbugs</groupId>
diff --git a/log4j-weaver/pom.xml b/log4j-weaver/pom.xml
new file mode 100644
index 0000000..3b8b3d6
--- /dev/null
+++ b/log4j-weaver/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.logging.log4j</groupId>
+    <artifactId>log4j-transform-parent</artifactId>
+    <version>${revision}</version>
+    <relativePath>../log4j-transform-parent</relativePath>
+  </parent>
+  <artifactId>log4j-weaver</artifactId>
+  <name>Apache Log4j Weaving Tools</name>
+  <description>The Apache Log4j Weaving Tools for logging frameworks</description>
+  <dependencies>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm-commons</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm-util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.github.spotbugs</groupId>
+      <artifactId>spotbugs-annotations</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/ClassConversionHandler.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/ClassConversionHandler.java
new file mode 100644
index 0000000..e88f654
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/ClassConversionHandler.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+/**
+ * Handles rewriting the methods of a specific class.
+ */
+public interface ClassConversionHandler {
+
+    /**
+     * Gets the internal name of the class supported by this handler.
+     */
+    String getOwner();
+
+    /**
+     * Handles a method call
+     *
+     * @param mv         a method visitor
+     * @param name       the name of the method
+     * @param descriptor the descriptor of the method
+     */
+    void handleMethodInstruction(LocationMethodVisitor mv, String name, String descriptor);
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/Constants.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/Constants.java
new file mode 100644
index 0000000..ec964ca
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/Constants.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.Method;
+
+public class Constants {
+
+    public static final Type[] EMPTY_ARRAY = new Type[0];
+    public static final String LOCATION_CACHE_SUFFIX = "$$Log4j2$$Cache";
+
+    // JDK types
+    public static final Type OBJECT_TYPE = Type.getType(Object.class);
+    public static final Type OBJECT_ARRAY_TYPE = Type.getType(Object[].class);
+    public static final Type STACK_TRACE_ELEMENT_TYPE = Type.getType(StackTraceElement.class);
+    public static final Type STACK_TRACE_ELEMENT_ARRAY_TYPE = Type.getType(StackTraceElement[].class);
+    public static final Type STRING_TYPE = Type.getType(String.class);
+    public static final Type THROWABLE_TYPE = Type.getType(Throwable.class);
+
+    // Log4j2 types
+    public static final Type ENTRY_MESSAGE_TYPE = Type.getObjectType("org/apache/logging/log4j/message/EntryMessage");
+    public static final Type EXIT_MESSAGE_TYPE = Type.getObjectType("org/apache/logging/log4j/message/ExitMessage");
+    public static final Type FLOW_MESSAGE_FACTORY_TYPE = Type
+            .getObjectType("org/apache/logging/log4j/message/FlowMessageFactory");
+    public static final Type LOG_BUILDER_TYPE = Type.getObjectType("org/apache/logging/log4j/LogBuilder");
+    public static final Type LEVEL_TYPE = Type.getObjectType("org/apache/logging/log4j/Level");
+    public static final Type LOGGER_TYPE = Type.getObjectType("org/apache/logging/log4j/Logger");
+    public static final Type LOGGING_SYSTEM_TYPE = Type.getObjectType("org/apache/logging/log4j/spi/LoggingSystem");
+    public static final Type MARKER_TYPE = Type.getObjectType("org/apache/logging/log4j/Marker");
+    public static final Type MESSAGE_TYPE = Type.getObjectType("org/apache/logging/log4j/message/Message");
+    public static final Type MESSAGE_FACTORY_TYPE = Type
+            .getObjectType("org/apache/logging/log4j/message/MessageFactory");
+    public static final Type MESSAGE_SUPPLIER_TYPE = Type
+            .getObjectType("org/apache/logging/log4j/util/MessageSupplier");
+    public static final Type SUPPLIER_TYPE = Type.getObjectType("org/apache/logging/log4j/util/Supplier");
+    public static final Type SUPPLIER_ARRAY_TYPE = Type.getType("[" + SUPPLIER_TYPE.getDescriptor());
+
+    // LogBuilder methods types
+    private static final String NO_ARGS_DESC = Type.getMethodDescriptor(LOG_BUILDER_TYPE);
+    public static final Method AT_DEBUG_METHOD = new Method("atDebug", NO_ARGS_DESC);
+    public static final Method AT_ERROR_METHOD = new Method("atError", NO_ARGS_DESC);
+    public static final Method AT_FATAL_METHOD = new Method("atFatal", NO_ARGS_DESC);
+    public static final Method AT_INFO_METHOD = new Method("atInfo", NO_ARGS_DESC);
+    public static final Method AT_TRACE_METHOD = new Method("atTrace", NO_ARGS_DESC);
+    public static final Method AT_WARN_METHOD = new Method("atWarn", NO_ARGS_DESC);
+    public static final Method AT_LEVEL_METHOD = new Method("atLevel",
+            Type.getMethodDescriptor(LOG_BUILDER_TYPE, LEVEL_TYPE));
+    public static final Method WITH_LOCATION_METHOD = new Method("withLocation",
+            Type.getMethodDescriptor(LOG_BUILDER_TYPE, STACK_TRACE_ELEMENT_TYPE));
+    public static final Method WITH_MARKER_METHOD = new Method("withMarker",
+            Type.getMethodDescriptor(LOG_BUILDER_TYPE, MARKER_TYPE));
+    public static final Method WITH_THROWABLE_METHOD = new Method("withThrowable",
+            Type.getMethodDescriptor(LOG_BUILDER_TYPE, THROWABLE_TYPE));
+    public static final Method LOG_AND_GET_METHOD = new Method("logAndGet",
+            Type.getMethodDescriptor(MESSAGE_TYPE, SUPPLIER_TYPE));
+
+    private Constants() {
+        // prevent instantiation
+    }
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/ConversionException.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/ConversionException.java
new file mode 100644
index 0000000..7620e7c
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/ConversionException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+/**
+ * Thrown by the converter, when it encounters some logging framework feature that is not supported.
+ */
+public class ConversionException extends RuntimeException {
+
+    private static final long serialVersionUID = -177281028782936849L;
+
+    public ConversionException(String message) {
+        super(message);
+    }
+
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationCacheGenerator.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationCacheGenerator.java
new file mode 100644
index 0000000..91f5e67
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationCacheGenerator.java
@@ -0,0 +1,281 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.InstructionAdapter;
+
+import static org.apache.logging.log4j.weaver.Constants.ENTRY_MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.EXIT_MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.FLOW_MESSAGE_FACTORY_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.LOGGER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.OBJECT_ARRAY_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.OBJECT_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STACK_TRACE_ELEMENT_ARRAY_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STACK_TRACE_ELEMENT_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STRING_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.SUPPLIER_ARRAY_TYPE;
+
+public class LocationCacheGenerator {
+
+    private static final Type LAMBDA_UTIL_TYPE = Type.getObjectType("org/apache/logging/log4j/util/LambdaUtil");
+    private static final Type STRING_FORMATTER_MESSAGE_FACTORY_TYPE = Type
+            .getObjectType("org/apache/logging/log4j/message/StringFormatterMessageFactory");
+    private static final String LOCATION_FIELD = "locations";
+
+    private final Map<String, LocationCacheContents> locationCacheClasses = new ConcurrentHashMap<>();
+
+    public LocationCacheValue addLocation(final String internalClassName, final String methodName,
+            final String fileName, final int lineNumber) {
+        final String cacheClassName = getCacheClassName(internalClassName);
+        final LocationCacheContents contents = locationCacheClasses.computeIfAbsent(cacheClassName,
+                k -> new LocationCacheContents());
+        final int index = contents.addLocation(internalClassName, methodName, fileName, lineNumber);
+        return new LocationCacheValue(cacheClassName, LOCATION_FIELD, index);
+    }
+
+    public Handle createLambda(String internalClassName, SupplierLambdaType type) {
+        final String cacheClassName = getCacheClassName(internalClassName);
+        final LocationCacheContents contents = locationCacheClasses.computeIfAbsent(cacheClassName,
+                k -> new LocationCacheContents());
+        contents.addLambda(type);
+        final String methodName = type.name().toLowerCase();
+        final String methodDescriptor = Type.getMethodDescriptor(MESSAGE_TYPE, type.getArgumentTypes());
+        switch (type) {
+            case FORMATTED_MESSAGE:
+            case ENTRY_MESSAGE_MESSAGE:
+            case ENTRY_MESSAGE_STRING_OBJECTS:
+            case ENTRY_MESSAGE_STRING_SUPPLIERS:
+            case EXIT_MESSAGE_ENTRY_MESSAGE:
+            case EXIT_MESSAGE_MESSAGE:
+            case EXIT_MESSAGE_OBJECT_ENTRY_MESSAGE:
+            case EXIT_MESSAGE_OBJECT_MESSAGE:
+            case EXIT_MESSAGE_STRING_OBJECT:
+                return new Handle(Opcodes.H_INVOKESTATIC, cacheClassName, methodName, methodDescriptor, false);
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+    public Map<String, byte[]> generateClasses() {
+        return locationCacheClasses.entrySet()
+                .parallelStream()
+                .collect(Collectors.toMap(Entry::getKey, e -> generateCacheClass(e.getKey(), e.getValue())));
+    }
+
+    private static byte[] generateCacheClass(final String innerClassName, final LocationCacheContents contents) {
+        final ClassWriter cv = new ClassWriter(0);
+        cv.visit(Opcodes.V1_8, 0, innerClassName, null, OBJECT_TYPE.getInternalName(), null);
+        // Write locations field
+        final List<StackTraceElement> locations = contents.getLocations();
+        writeLocations(innerClassName, cv, locations);
+        // We add lambdas to this class
+        final Set<SupplierLambdaType> lambdas = contents.getLambdas();
+        for (final SupplierLambdaType type : lambdas) {
+            final InstructionAdapter mv = new InstructionAdapter(cv.visitMethod(Opcodes.ACC_STATIC,
+                    type.name().toLowerCase(), type.getImplementationMethodDescriptor(), null, null));
+            switch (type) {
+                case FORMATTED_MESSAGE:
+                    writeFormattedMessage(mv);
+                    break;
+                case ENTRY_MESSAGE_MESSAGE:
+                case ENTRY_MESSAGE_STRING_OBJECTS:
+                case EXIT_MESSAGE_ENTRY_MESSAGE:
+                case EXIT_MESSAGE_MESSAGE:
+                case EXIT_MESSAGE_OBJECT_ENTRY_MESSAGE:
+                case EXIT_MESSAGE_OBJECT_MESSAGE:
+                case EXIT_MESSAGE_STRING_OBJECT:
+                    writeEntryExitMessage(mv, type);
+                    break;
+                case ENTRY_MESSAGE_STRING_SUPPLIERS:
+                    writeEntryMessageSuppliers(mv);
+                    break;
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+        cv.visitEnd();
+        return cv.toByteArray();
+    }
+
+    private static void writeLocations(final String innerClassName, final ClassVisitor cv,
+            final List<StackTraceElement> locations) {
+        cv.visitField(Opcodes.ACC_STATIC, LOCATION_FIELD, STACK_TRACE_ELEMENT_ARRAY_TYPE.getInternalName(), null, null)
+                .visitEnd();
+        final InstructionAdapter mv = new InstructionAdapter(
+                cv.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null));
+        mv.visitCode();
+        mv.visitMaxs(9, 0);
+        mv.iconst(locations.size());
+        mv.newarray(STACK_TRACE_ELEMENT_TYPE);
+        for (int i = 0; i < locations.size(); i++) {
+            final StackTraceElement location = locations.get(i);
+            mv.dup();
+            mv.iconst(i);
+            mv.anew(STACK_TRACE_ELEMENT_TYPE);
+            mv.dup();
+            mv.aconst(location.getClassName());
+            mv.aconst(location.getMethodName());
+            mv.aconst(location.getFileName());
+            mv.iconst(location.getLineNumber());
+            mv.invokespecial(STACK_TRACE_ELEMENT_TYPE.getInternalName(), "<init>",
+                    Type.getMethodDescriptor(Type.VOID_TYPE, STRING_TYPE, STRING_TYPE, STRING_TYPE, Type.INT_TYPE),
+                    false);
+            mv.visitInsn(Opcodes.AASTORE);
+        }
+        mv.putstatic(innerClassName, LOCATION_FIELD, STACK_TRACE_ELEMENT_ARRAY_TYPE.getInternalName());
+        mv.areturn(Type.VOID_TYPE);
+        mv.visitEnd();
+    }
+
+    private static void writeFormattedMessage(final InstructionAdapter mv) {
+        mv.visitCode();
+        mv.visitMaxs(3, 2);
+        mv.getstatic(STRING_FORMATTER_MESSAGE_FACTORY_TYPE.getInternalName(), "INSTANCE",
+                STRING_FORMATTER_MESSAGE_FACTORY_TYPE.getDescriptor());
+        mv.load(0, STRING_TYPE);
+        mv.load(1, OBJECT_ARRAY_TYPE);
+        mv.invokevirtual(STRING_FORMATTER_MESSAGE_FACTORY_TYPE.getInternalName(), "newMessage",
+                Type.getMethodType(MESSAGE_TYPE, STRING_TYPE, OBJECT_ARRAY_TYPE).getDescriptor(), false);
+        mv.areturn(MESSAGE_TYPE);
+        mv.visitEnd();
+    }
+
+    private static void writeEntryExitMessage(final InstructionAdapter mv, final SupplierLambdaType type) {
+        final Type[] args = type.getArgumentTypes();
+        mv.visitCode();
+        mv.visitMaxs(args.length, args.length);
+        mv.load(0, LOGGER_TYPE);
+        mv.invokeinterface(LOGGER_TYPE.getInternalName(), "getFlowMessageFactory",
+                Type.getMethodDescriptor(FLOW_MESSAGE_FACTORY_TYPE));
+        for (int i = 1; i < args.length; i++) {
+            mv.load(i, args[i]);
+        }
+        final boolean isEntry = type.name().startsWith("ENTRY");
+        final String methodName = isEntry ? "newEntryMessage" : "newExitMessage";
+        mv.invokeinterface(FLOW_MESSAGE_FACTORY_TYPE.getInternalName(), methodName, Type.getMethodDescriptor(
+                isEntry ? ENTRY_MESSAGE_TYPE : EXIT_MESSAGE_TYPE, Arrays.copyOfRange(args, 1, args.length)));
+        mv.areturn(MESSAGE_TYPE);
+        mv.visitEnd();
+    }
+
+    private static void writeEntryMessageSuppliers(final InstructionAdapter mv) {
+        mv.visitCode();
+        mv.visitMaxs(3, 3);
+        mv.load(0, LOGGER_TYPE);
+        mv.invokeinterface(LOGGER_TYPE.getInternalName(), "getFlowMessageFactory",
+                Type.getMethodDescriptor(FLOW_MESSAGE_FACTORY_TYPE));
+        mv.load(1, STRING_TYPE);
+        mv.load(2, SUPPLIER_ARRAY_TYPE);
+        mv.invokestatic(LAMBDA_UTIL_TYPE.getInternalName(), "getAll",
+                Type.getMethodDescriptor(OBJECT_ARRAY_TYPE, SUPPLIER_ARRAY_TYPE), false);
+        mv.invokeinterface(FLOW_MESSAGE_FACTORY_TYPE.getInternalName(), "newEntryMessage",
+                Type.getMethodDescriptor(ENTRY_MESSAGE_TYPE, STRING_TYPE, OBJECT_ARRAY_TYPE));
+        mv.areturn(MESSAGE_TYPE);
+        mv.visitEnd();
+    }
+
+    private static String getCacheClassName(final String internalClassName) {
+        return StringUtils.substringBefore(internalClassName, '$') + Constants.LOCATION_CACHE_SUFFIX;
+    }
+
+    public static Path getCacheClassFile(final Path classFile) {
+        final Path fileName = classFile.getFileName();
+        if (fileName == null) {
+            throw new IllegalArgumentException("The 'classFile' parameter is an empty path.");
+        }
+        final String cacheFileName = LocationCacheGenerator
+                .getCacheClassName(StringUtils.removeEnd(fileName.toString(), ".class")) + ".class";
+        return classFile.resolveSibling(cacheFileName);
+    }
+
+    public static class LocationCacheValue {
+        private final String internalClassName;
+        private final String fieldName;
+        private final int index;
+
+        private LocationCacheValue(String internalClassName, String fieldName, int index) {
+            super();
+            this.internalClassName = internalClassName;
+            this.fieldName = fieldName;
+            this.index = index;
+        }
+
+        public String getInternalClassName() {
+            return internalClassName;
+        }
+
+        public Type getType() {
+            return Type.getObjectType(internalClassName);
+        }
+
+        public String getFieldName() {
+            return fieldName;
+        }
+
+        public int getIndex() {
+            return index;
+        }
+    }
+
+    /**
+     * Describes the methods and fields of a specific location cache class.
+     *
+     */
+    private static class LocationCacheContents {
+        private final List<StackTraceElement> locations = new CopyOnWriteArrayList<>();
+        private Set<SupplierLambdaType> lambdas = EnumSet.noneOf(SupplierLambdaType.class);
+
+        public int addLocation(final String internalClassName, final String methodName, final String fileName,
+                final int lineNumber) {
+            final StackTraceElement location = new StackTraceElement(internalClassName.replaceAll("/", "."), methodName,
+                    fileName, lineNumber);
+            locations.add(location);
+            return locations.indexOf(location);
+        }
+
+        public List<StackTraceElement> getLocations() {
+            return locations;
+        }
+
+        public boolean addLambda(SupplierLambdaType type) {
+            return lambdas.add(type);
+        }
+
+        public Set<SupplierLambdaType> getLambdas() {
+            return lambdas;
+        }
+    }
+
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationClassConverter.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationClassConverter.java
new file mode 100644
index 0000000..52a3377
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationClassConverter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.logging.log4j.weaver.log4j2.LogBuilderConversionHandler;
+import org.apache.logging.log4j.weaver.log4j2.LoggerConversionHandler;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+public class LocationClassConverter {
+
+    /**
+     * Adds location information to a classfile.
+     *
+     * @param src           original classfile
+     * @param dest          transformed classfile
+     * @param locationCache a container for location data
+     */
+    public void convert(InputStream src, OutputStream dest, LocationCacheGenerator locationCache) throws IOException {
+        final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+        final LocationClassVisitor converter = new LocationClassVisitor(writer, locationCache);
+        converter.addClassConversionHandler(new LoggerConversionHandler());
+        converter.addClassConversionHandler(new LogBuilderConversionHandler());
+        new ClassReader(src).accept(converter, ClassReader.EXPAND_FRAMES);
+
+        dest.write(writer.toByteArray());
+    }
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationClassVisitor.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationClassVisitor.java
new file mode 100644
index 0000000..3655678
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationClassVisitor.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.weaver.LocationCacheGenerator.LocationCacheValue;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import static org.apache.logging.log4j.weaver.Constants.*;
+
+public class LocationClassVisitor extends ClassVisitor {
+
+    private final LocationCacheGenerator locationCache;
+    private final Map<String, ClassConversionHandler> conversionHandlers;
+
+    private String fileName;
+    private String declaringClass;
+    private String methodName;
+
+    protected LocationClassVisitor(ClassVisitor cv, LocationCacheGenerator locationCache) {
+        super(Opcodes.ASM9, cv);
+        this.locationCache = locationCache;
+        this.conversionHandlers = new HashMap<>();
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        this.declaringClass = name;
+        super.visit(version, access, name, signature, superName, interfaces);
+    }
+
+    @Override
+    public void visitSource(String source, String debug) {
+        this.fileName = source;
+        super.visitSource(source, debug);
+    }
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
+            String[] exceptions) {
+        this.methodName = name;
+        final MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
+        return mv != null
+                ? new LocationMethodVisitor(this, Collections.unmodifiableMap(conversionHandlers), mv, access, name,
+                        descriptor)
+                : null;
+    }
+
+    public void addClassConversionHandler(final ClassConversionHandler handler) {
+        this.conversionHandlers.put(handler.getOwner(), handler);
+    }
+
+    public LocationCacheValue addStackTraceElement(final int lineNumber) {
+        return locationCache.addLocation(declaringClass, methodName, fileName, lineNumber);
+    }
+
+    public Handle createLambda(SupplierLambdaType type) {
+        switch (type) {
+            case MESSAGE_SUPPLIER:
+                return new Handle(Opcodes.H_INVOKEINTERFACE, MESSAGE_SUPPLIER_TYPE.getInternalName(), "get",
+                        Type.getMethodDescriptor(MESSAGE_TYPE), true);
+            default:
+                return locationCache.createLambda(declaringClass, type);
+        }
+    }
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationMethodVisitor.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationMethodVisitor.java
new file mode 100644
index 0000000..68e9d49
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/LocationMethodVisitor.java
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.LambdaMetafactory;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Map;
+
+import org.apache.logging.log4j.weaver.LocationCacheGenerator.LocationCacheValue;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import static org.apache.logging.log4j.weaver.Constants.LOG_BUILDER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.OBJECT_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STACK_TRACE_ELEMENT_ARRAY_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STACK_TRACE_ELEMENT_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STRING_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.WITH_LOCATION_METHOD;
+
+public class LocationMethodVisitor extends GeneratorAdapter {
+
+    // Programmatically define LAMBDA_METAFACTORY_HANDLE
+    private static Type SUPPLIER_OF_OBJECT_TYPE = Type.getMethodType(OBJECT_TYPE);
+    private static Type SUPPLIER_OF_MESSAGE_TYPE = Type.getMethodType(MESSAGE_TYPE);
+    private static final Type LAMBDA_METAFACTORY_TYPE = Type.getType(LambdaMetafactory.class);
+    private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class);
+    private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class);
+    private static final String LAMBDA_METAFACTORY_DESC = Type.getMethodDescriptor(Type.getType(CallSite.class),
+            Type.getType(MethodHandles.Lookup.class), STRING_TYPE, METHOD_TYPE_TYPE, METHOD_TYPE_TYPE,
+            METHOD_HANDLE_TYPE, METHOD_TYPE_TYPE);
+    private static final Handle LAMBDA_METAFACTORY_HANDLE = new Handle(Opcodes.H_INVOKESTATIC,
+            LAMBDA_METAFACTORY_TYPE.getInternalName(), "metafactory", LAMBDA_METAFACTORY_DESC, false);
+
+    private final LocationClassVisitor locationClassVisitor;
+    private final Map<String, ClassConversionHandler> handlers;
+
+    // A pool of local variables
+    private final Integer[] localVariables = new Integer[12];
+    private final Label[] startLabels = new Label[12];
+    // Next available variable index
+    private int nextVariable = 0;
+
+    private int lineNumber;
+    private Label currentLabel;
+
+    protected LocationMethodVisitor(final LocationClassVisitor locationClassVisitor,
+            final Map<String, ClassConversionHandler> handlers, final MethodVisitor mv, final int access,
+            final String name, final String descriptor) {
+        super(Opcodes.ASM9, mv, access, name, descriptor);
+        this.locationClassVisitor = locationClassVisitor;
+        this.handlers = handlers;
+    }
+
+    @Override
+    public void visitLineNumber(int line, Label start) {
+        this.lineNumber = line;
+        super.visitLineNumber(line, start);
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
+        resetLocals();
+        final ClassConversionHandler handler = handlers.get(owner);
+        if (handler != null) {
+            handler.handleMethodInstruction(this, name, descriptor);
+        } else {
+            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+        }
+    }
+
+    /**
+     * Assuming the top of the stack holds a {@code LogBuilder}, add location
+     * information to it.
+     */
+    public void storeLocation() {
+        final LocationCacheValue location = locationClassVisitor.addStackTraceElement(lineNumber);
+        getStatic(location.getType(), location.getFieldName(), STACK_TRACE_ELEMENT_ARRAY_TYPE);
+        push(location.getIndex());
+        arrayLoad(STACK_TRACE_ELEMENT_TYPE);
+        invokeInterface(LOG_BUILDER_TYPE, WITH_LOCATION_METHOD);
+    }
+
+    @Override
+    @SuppressFBWarnings(value = {"EI_EXPOSE_REP2"})
+    public void visitLabel(Label label) {
+        currentLabel = label;
+        super.visitLabel(label);
+    }
+
+    @Override
+    public void visitEnd() {
+        for (int i = 0; i < startLabels.length; i++) {
+            final Label label = startLabels[i];
+            if (label != null) {
+                // the generator adapter uses different variable indexes
+                // so we use 'mv' directly
+                mv.visitLocalVariable("log4j2$$p" + i, OBJECT_TYPE.getDescriptor(), null, label, currentLabel,
+                        localVariables[i]);
+            }
+        }
+        super.visitEnd();
+    }
+
+    private void resetLocals() {
+        nextVariable = 0;
+    }
+
+    public int nextLocal() {
+        Integer varIndex = localVariables[nextVariable];
+        if (varIndex == null) {
+            varIndex = newLocal(OBJECT_TYPE);
+            localVariables[nextVariable] = varIndex;
+            // remember first usage of variable
+            startLabels[nextVariable] = currentLabel;
+        }
+        nextVariable++;
+        return varIndex;
+    }
+
+    public void invokeSupplierLambda(SupplierLambdaType type) {
+        invokeDynamic("get",
+                type.getInvokedMethodDescriptor(),
+                LAMBDA_METAFACTORY_HANDLE,
+                SUPPLIER_OF_OBJECT_TYPE,
+                locationClassVisitor.createLambda(type),
+                SUPPLIER_OF_MESSAGE_TYPE);
+    }
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/SupplierLambdaType.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/SupplierLambdaType.java
new file mode 100644
index 0000000..1a2503a
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/SupplierLambdaType.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import org.objectweb.asm.Type;
+
+import static org.apache.logging.log4j.weaver.Constants.ENTRY_MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.LOGGER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.MESSAGE_SUPPLIER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.OBJECT_ARRAY_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.OBJECT_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STRING_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.SUPPLIER_ARRAY_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.SUPPLIER_TYPE;
+
+/**
+ * An enumeration of {@code org.apache.logging.log4j.util.Supplier<Message>} lambdas, named after the type of message and parameter types.
+ *
+ */
+public enum SupplierLambdaType {
+    MESSAGE_SUPPLIER(MESSAGE_SUPPLIER_TYPE),
+    FORMATTED_MESSAGE(STRING_TYPE, OBJECT_ARRAY_TYPE),
+    ENTRY_MESSAGE_MESSAGE(LOGGER_TYPE, MESSAGE_TYPE),
+    ENTRY_MESSAGE_STRING_OBJECTS(LOGGER_TYPE, STRING_TYPE, OBJECT_ARRAY_TYPE),
+    EXIT_MESSAGE_ENTRY_MESSAGE(LOGGER_TYPE, ENTRY_MESSAGE_TYPE),
+    EXIT_MESSAGE_MESSAGE(LOGGER_TYPE, MESSAGE_TYPE),
+    EXIT_MESSAGE_OBJECT_ENTRY_MESSAGE(LOGGER_TYPE, OBJECT_TYPE, ENTRY_MESSAGE_TYPE),
+    EXIT_MESSAGE_OBJECT_MESSAGE(LOGGER_TYPE, OBJECT_TYPE, MESSAGE_TYPE),
+    EXIT_MESSAGE_STRING_OBJECT(LOGGER_TYPE, STRING_TYPE, OBJECT_TYPE),
+    ENTRY_MESSAGE_STRING_SUPPLIERS(LOGGER_TYPE, STRING_TYPE, SUPPLIER_ARRAY_TYPE);
+
+    private final Type[] argumentTypes;
+
+    private SupplierLambdaType(final Type... argumentTypes) {
+        this.argumentTypes = argumentTypes;
+    }
+
+    /**
+     * Returns the descriptor of the invokedynamic call.
+     */
+    public String getInvokedMethodDescriptor() {
+        return Type.getMethodDescriptor(SUPPLIER_TYPE, argumentTypes);
+    }
+
+    /**
+     * Returns the descriptor of the implementation method.
+     */
+    public String getImplementationMethodDescriptor() {
+        return Type.getMethodDescriptor(MESSAGE_TYPE, argumentTypes);
+    }
+
+    public Type[] getArgumentTypes() {
+        return argumentTypes.clone();
+    }
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandler.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandler.java
new file mode 100644
index 0000000..4bf80af
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver.log4j2;
+
+import org.apache.logging.log4j.weaver.ClassConversionHandler;
+import org.apache.logging.log4j.weaver.LocationMethodVisitor;
+
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.Method;
+
+import static org.apache.logging.log4j.weaver.Constants.LOG_BUILDER_TYPE;
+
+public class LogBuilderConversionHandler implements ClassConversionHandler {
+
+    @Override
+    public String getOwner() {
+        return LOG_BUILDER_TYPE.getInternalName();
+    }
+
+    @Override
+    public void handleMethodInstruction(LocationMethodVisitor mv, String name, String descriptor) {
+        if ("withLocation".equals(name) && Type.getMethodDescriptor(LOG_BUILDER_TYPE).equals(descriptor)) {
+            return;
+        }
+        mv.invokeInterface(LOG_BUILDER_TYPE, new Method(name, descriptor));
+    }
+
+}
diff --git a/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandler.java b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandler.java
new file mode 100644
index 0000000..f1c42d0
--- /dev/null
+++ b/log4j-weaver/src/main/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandler.java
@@ -0,0 +1,343 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver.log4j2;
+
+import java.util.Arrays;
+
+import org.apache.logging.log4j.weaver.ClassConversionHandler;
+import org.apache.logging.log4j.weaver.Constants;
+import org.apache.logging.log4j.weaver.ConversionException;
+import org.apache.logging.log4j.weaver.LocationMethodVisitor;
+import org.apache.logging.log4j.weaver.SupplierLambdaType;
+
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.Method;
+
+import static org.apache.logging.log4j.weaver.Constants.AT_DEBUG_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.AT_ERROR_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.AT_FATAL_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.AT_INFO_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.AT_LEVEL_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.AT_TRACE_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.AT_WARN_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.ENTRY_MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.LEVEL_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.LOGGER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.LOG_AND_GET_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.LOG_BUILDER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.MARKER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.MESSAGE_SUPPLIER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.MESSAGE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.OBJECT_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.STRING_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.SUPPLIER_ARRAY_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.SUPPLIER_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.THROWABLE_TYPE;
+import static org.apache.logging.log4j.weaver.Constants.WITH_MARKER_METHOD;
+import static org.apache.logging.log4j.weaver.Constants.WITH_THROWABLE_METHOD;
+
+public class LoggerConversionHandler implements ClassConversionHandler {
+
+    private static final String CATCHING = "Catching";
+    private static final String CATCHING_MARKER = "CATCHING_MARKER";
+    private static final String ENTRY_MARKER = "ENTRY_MARKER";
+    private static final String EXIT_MARKER = "EXIT_MARKER";
+    private static final String THROWING = "Throwing";
+    private static final String THROWING_MARKER = "THROWING_MARKER";
+    // Argument list of `LogBuilder.log(String, Supplier...)`
+    private static final Type[] LOG_BUILDER_LOG_STRING_SUPPLIER = new Type[] { STRING_TYPE, SUPPLIER_ARRAY_TYPE };
+    // Argument list of `LogBuilder.log(Supplier<Message>)`
+    private static final Type[] LOG_BUILDER_LOG_SUPPLIER_MESSAGE = new Type[] { SUPPLIER_TYPE };
+    private static final Method LOG_BUILDER_LOG_SUPPLIER_METHOD = new Method("log",
+            Type.getMethodDescriptor(Type.VOID_TYPE, LOG_BUILDER_LOG_SUPPLIER_MESSAGE));
+    private static final Method LOG_BUILDER_LOG_STRING_METHOD = new Method("log",
+            Type.getMethodDescriptor(Type.VOID_TYPE, STRING_TYPE));
+    private static final Type ABSTRACT_LOGGER_TYPE = Type.getObjectType("org/apache/logging/log4j/spi/AbstractLogger");
+    private static final Type[] MESSAGE_OBJECT_ARRAY = { MESSAGE_TYPE, OBJECT_TYPE };
+
+    @Override
+    public String getOwner() {
+        return Constants.LOGGER_TYPE.getInternalName();
+    }
+
+    private void createLogBuilder(LocationMethodVisitor mv, String methodName) {
+        final Method method;
+        switch (methodName) {
+            case "debug":
+                method = AT_DEBUG_METHOD;
+                break;
+            case "error":
+                method = AT_ERROR_METHOD;
+                break;
+            case "fatal":
+                method = AT_FATAL_METHOD;
+                break;
+            case "info":
+                method = AT_INFO_METHOD;
+                break;
+            case "log":
+                method = AT_LEVEL_METHOD;
+                break;
+            case "trace":
+                method = AT_TRACE_METHOD;
+                break;
+            case "warn":
+                method = AT_WARN_METHOD;
+                break;
+            default:
+                throw new ConversionException("Unknown logging method " + methodName);
+        }
+        mv.invokeInterface(LOGGER_TYPE, method);
+    }
+
+    @Override
+    public void handleMethodInstruction(LocationMethodVisitor mv, String name, String descriptor) {
+        switch (name) {
+            case "debug":
+            case "error":
+            case "fatal":
+            case "info":
+            case "log":
+            case "trace":
+            case "warn":
+                handleLogMethods(mv, name, descriptor);
+                break;
+            case "printf":
+                handlePrintfMethods(mv, descriptor);
+                break;
+            case "always":
+            case "atDebug":
+            case "atError":
+            case "atFatal":
+            case "atInfo":
+            case "atLevel":
+            case "atTrace":
+            case "atWarn":
+                mv.invokeInterface(LOGGER_TYPE, new Method(name, descriptor));
+                mv.storeLocation();
+                break;
+            case "catching":
+            case "throwing":
+                handleCatchingThrowing(mv, descriptor, "throwing".equals(name));
+                break;
+            case "isDebugEnabled":
+            case "isEnabled":
+            case "isErrorEnabled":
+            case "isFatalEnabled":
+            case "isInfoEnabled":
+            case "isTraceEnabled":
+            case "isWarnEnabled":
+            case "logMessage":
+                // These are NOPs
+                mv.invokeInterface(LOGGER_TYPE, new Method(name, descriptor));
+                break;
+            case "traceEntry":
+                handleTraceEntry(mv, descriptor);
+                break;
+            case "traceExit":
+                handleTraceExit(mv, descriptor);
+                break;
+            default:
+                throw new ConversionException("Unsupported method 'org.apache.logging.log4j.Logger#" + name + "'.");
+        }
+    }
+
+    /**
+     * Rewrites the most common methods: {@code log} and its level specializations.
+     */
+    private void handleLogMethods(LocationMethodVisitor mv, String name, String descriptor) {
+        final Type[] types = Type.getArgumentTypes(descriptor);
+        final int[] varIndexes = new int[types.length];
+        int from = types.length > 0 && types[0].equals(LEVEL_TYPE) ? 1 : 0;
+        int to = types.length;
+        // Store arguments to local variables
+        // TODO: most of the time there is a more efficient way
+        for (int i = to - 1; i >= from; i--) {
+            varIndexes[i] = mv.nextLocal();
+            mv.storeLocal(varIndexes[i], types[i]);
+        }
+        // create the LogBuilder
+        createLogBuilder(mv, name);
+        mv.storeLocation();
+        // Marker argument
+        if (from < to && types[from].equals(MARKER_TYPE)) {
+            mv.loadLocal(varIndexes[from], MARKER_TYPE);
+            mv.invokeInterface(LOG_BUILDER_TYPE, WITH_MARKER_METHOD);
+            from++;
+        }
+        // Throwable argument
+        if (from < to && types[to - 1].equals(THROWABLE_TYPE)) {
+            mv.loadLocal(varIndexes[to - 1], THROWABLE_TYPE);
+            mv.invokeInterface(LOG_BUILDER_TYPE, WITH_THROWABLE_METHOD);
+            to--;
+        }
+        // Call log(...)
+        final Type[] arguments;
+        // We need to replace (Supplier<?>) with ("{}", Supplier<?>)
+        if (SUPPLIER_TYPE.equals(types[from])) {
+            mv.push("{}");
+            mv.push(1);
+            mv.newArray(SUPPLIER_TYPE);
+            mv.dup();
+            mv.push(0);
+            mv.loadLocal(varIndexes[from], types[from]);
+            mv.arrayStore(SUPPLIER_TYPE);
+            arguments = LOG_BUILDER_LOG_STRING_SUPPLIER;
+            // We need to convert MessageSupplier to Supplier<Message>
+        } else if (MESSAGE_SUPPLIER_TYPE.equals(types[from])) {
+            mv.loadLocal(varIndexes[from], types[from]);
+            mv.invokeSupplierLambda(SupplierLambdaType.MESSAGE_SUPPLIER);
+            arguments = LOG_BUILDER_LOG_SUPPLIER_MESSAGE;
+        } else {
+            for (int i = from; i < to; i++) {
+                mv.loadLocal(varIndexes[i], types[i]);
+            }
+            arguments = Arrays.copyOfRange(types, from, to);
+        }
+        final Method logMethod = new Method("log", Type.VOID_TYPE, arguments);
+        mv.invokeInterface(LOG_BUILDER_TYPE, logMethod);
+    }
+
+    private void handlePrintfMethods(LocationMethodVisitor mv, String descriptor) {
+        final Type[] types = Type.getArgumentTypes(descriptor);
+        // Transform the last two arguments into a supplier
+        mv.invokeSupplierLambda(SupplierLambdaType.FORMATTED_MESSAGE);
+        int supplierIndex = mv.nextLocal();
+        mv.storeLocal(supplierIndex, SUPPLIER_TYPE);
+        int markerIndex = -1;
+        if (types[1].equals(MARKER_TYPE)) {
+            markerIndex = mv.nextLocal();
+            mv.storeLocal(markerIndex, MARKER_TYPE);
+        }
+        mv.invokeInterface(LOGGER_TYPE, AT_LEVEL_METHOD);
+        mv.storeLocation();
+        if (markerIndex >= 0) {
+            mv.loadLocal(markerIndex, MARKER_TYPE);
+            mv.invokeInterface(LOG_BUILDER_TYPE, WITH_MARKER_METHOD);
+        }
+        mv.loadLocal(supplierIndex, SUPPLIER_TYPE);
+        mv.invokeInterface(LOG_BUILDER_TYPE, LOG_BUILDER_LOG_SUPPLIER_METHOD);
+    }
+
+    private void handleCatchingThrowing(LocationMethodVisitor mv, String descriptor, boolean throwing) {
+        final boolean hasLevel = Type.getArgumentTypes(descriptor).length > 1;
+        final int throwableIndex = mv.nextLocal();
+        mv.storeLocal(throwableIndex, THROWABLE_TYPE);
+        if (hasLevel) {
+            mv.invokeInterface(LOGGER_TYPE, AT_LEVEL_METHOD);
+        } else {
+            mv.invokeInterface(LOGGER_TYPE, AT_ERROR_METHOD);
+        }
+        mv.storeLocation();
+        mv.loadLocal(throwableIndex, THROWABLE_TYPE);
+        mv.invokeInterface(LOG_BUILDER_TYPE, WITH_THROWABLE_METHOD);
+        mv.getStatic(ABSTRACT_LOGGER_TYPE, throwing ? THROWING_MARKER : CATCHING_MARKER, MARKER_TYPE);
+        mv.invokeInterface(LOG_BUILDER_TYPE, WITH_MARKER_METHOD);
+        mv.push(throwing ? THROWING : CATCHING);
+        mv.invokeInterface(LOG_BUILDER_TYPE, LOG_BUILDER_LOG_STRING_METHOD);
+        if (throwing) {
+            mv.loadLocal(throwableIndex, THROWABLE_TYPE);
+        }
+    }
+
+    private void handleTraceEntry(LocationMethodVisitor mv, String descriptor) {
+        final Type[] types = Type.getArgumentTypes(descriptor);
+        final int[] vars = new int[types.length];
+        for (int i = vars.length - 1; i >= 0; i--) {
+            vars[i] = mv.nextLocal();
+            mv.storeLocal(vars[i]);
+        }
+        // only Logger on stack
+        mv.dup();
+        final int loggerIdx = mv.nextLocal();
+        mv.storeLocal(loggerIdx, LOGGER_TYPE);
+        mv.invokeInterface(LOGGER_TYPE, AT_TRACE_METHOD);
+        mv.storeLocation();
+        mv.getStatic(ABSTRACT_LOGGER_TYPE, ENTRY_MARKER, MARKER_TYPE);
+        mv.invokeInterface(LOG_BUILDER_TYPE, WITH_MARKER_METHOD);
+        mv.loadLocal(loggerIdx, LOGGER_TYPE);
+        if (types.length == 0) {
+            mv.push((String) null);
+            mv.push((String) null);
+            mv.invokeSupplierLambda(SupplierLambdaType.ENTRY_MESSAGE_STRING_OBJECTS);
+        } else if (types[0].equals(MESSAGE_TYPE)) {
+            mv.loadLocal(vars[0]);
+            mv.invokeSupplierLambda(SupplierLambdaType.ENTRY_MESSAGE_MESSAGE);
+        } else {
+            if (types.length == 1) {
+                mv.push((String) null);
+            }
+            for (int i = 0; i < vars.length; i++) {
+                mv.loadLocal(vars[i]);
+            }
+            final boolean usesSuppliers = types[types.length - 1].equals(SUPPLIER_ARRAY_TYPE);
+            mv.invokeSupplierLambda(usesSuppliers ? SupplierLambdaType.ENTRY_MESSAGE_STRING_SUPPLIERS
+                    : SupplierLambdaType.ENTRY_MESSAGE_STRING_OBJECTS);
+        }
+        mv.invokeInterface(LOG_BUILDER_TYPE, LOG_AND_GET_METHOD);
+    }
+
+    private void handleTraceExit(LocationMethodVisitor mv, String descriptor) {
+        final Type[] types = Type.getArgumentTypes(descriptor);
+        final int[] vars = new int[types.length];
+        for (int i = vars.length - 1; i >= 0; i--) {
+            vars[i] = mv.nextLocal();
+            mv.storeLocal(vars[i]);
+        }
+        // only Logger on stack
+        mv.dup();
+        final int loggerIdx = mv.nextLocal();
+        mv.storeLocal(loggerIdx, LOGGER_TYPE);
+        mv.invokeInterface(LOGGER_TYPE, AT_TRACE_METHOD);
+        mv.storeLocation();
+        mv.getStatic(ABSTRACT_LOGGER_TYPE, EXIT_MARKER, MARKER_TYPE);
+        mv.invokeInterface(LOG_BUILDER_TYPE, WITH_MARKER_METHOD);
+        mv.loadLocal(loggerIdx, LOGGER_TYPE);
+        if (types.length == 0) {
+            mv.push((String) null);
+            mv.push((String) null);
+            mv.invokeSupplierLambda(SupplierLambdaType.EXIT_MESSAGE_STRING_OBJECT);
+        } else if (Arrays.deepEquals(types, MESSAGE_OBJECT_ARRAY)) {
+            // Invert arguments
+            mv.loadLocal(vars[1]);
+            mv.loadLocal(vars[0]);
+            mv.invokeSupplierLambda(SupplierLambdaType.EXIT_MESSAGE_OBJECT_MESSAGE);
+        } else if (ENTRY_MESSAGE_TYPE.equals(types[0])) {
+            final boolean hasResult = types.length == 2;
+            if (hasResult) {
+                mv.loadLocal(vars[1]);
+            }
+            mv.loadLocal(vars[0]);
+            mv.invokeSupplierLambda(hasResult ? SupplierLambdaType.EXIT_MESSAGE_OBJECT_ENTRY_MESSAGE
+                    : SupplierLambdaType.EXIT_MESSAGE_ENTRY_MESSAGE);
+        } else {
+            final boolean hasFormat = STRING_TYPE.equals(types[0]);
+            if (hasFormat) {
+                mv.loadLocal(vars[0]);
+            } else {
+                mv.push((String) null);
+            }
+            mv.loadLocal(vars[hasFormat ? 1 : 0], OBJECT_TYPE);
+            mv.invokeSupplierLambda(SupplierLambdaType.EXIT_MESSAGE_STRING_OBJECT);
+        }
+        mv.invokeInterface(LOG_BUILDER_TYPE, LOG_BUILDER_LOG_SUPPLIER_METHOD);
+        // except void methods traceExit() and traceExit(EntryMessage)
+        if (types.length != 0 && (types.length > 1 || !ENTRY_MESSAGE_TYPE.equals(types[0]))) {
+            mv.loadLocal(vars[vars.length - 1]);
+        }
+    }
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/AbstractConversionHandlerTest.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/AbstractConversionHandlerTest.java
new file mode 100644
index 0000000..59f4e7b
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/AbstractConversionHandlerTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+public class AbstractConversionHandlerTest {
+
+    protected static Class<?> convertedClass;
+    protected static Object testObject;
+
+    protected static void transformClass(String internalName) throws Exception {
+        final TestClassLoader testCl = new TestClassLoader();
+
+        final ByteArrayOutputStream dest = new ByteArrayOutputStream();
+        final LocationClassConverter converter = new LocationClassConverter();
+        final LocationCacheGenerator locationCache = new LocationCacheGenerator();
+
+        getNestedClasses(internalName).forEach(classFile -> assertDoesNotThrow(() -> {
+            dest.reset();
+            converter.convert(Files.newInputStream(classFile), dest, locationCache);
+            testCl.defineClass(dest.toByteArray());
+        }));
+        locationCache.generateClasses().values().forEach(testCl::defineClass);
+        convertedClass = testCl.loadClass(internalName.replaceAll("/", "."));
+        testObject = assertDoesNotThrow(() -> convertedClass.getConstructor().newInstance());
+    }
+
+    private static Stream<Path> getNestedClasses(String internalName) throws Exception {
+        final Path topClass = Paths
+                .get(AbstractConversionHandlerTest.class.getClassLoader().getResource(internalName + ".class").toURI());
+        final String simpleClassName = Paths.get(internalName).getFileName().toString();
+        return Files.walk(topClass.getParent(), 1).filter(p -> {
+            final String nested = p.getFileName().toString();
+            return nested.startsWith(simpleClassName) && nested.endsWith(".class");
+        }).sorted();
+    }
+
+    private static class TestClassLoader extends ClassLoader {
+
+        public TestClassLoader() {
+            super(AbstractConversionHandlerTest.class.getClassLoader());
+        }
+
+        public Class<?> defineClass(byte[] bytes) {
+            return defineClass(null, bytes, 0, bytes.length);
+        }
+    }
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/NoLoggingExample.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/NoLoggingExample.java
new file mode 100644
index 0000000..a1c9994
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/NoLoggingExample.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+public class NoLoggingExample {
+
+    public String greet() {
+        return "Hello Log4j2!";
+    }
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/NoLoggingTest.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/NoLoggingTest.java
new file mode 100644
index 0000000..0ebc7d9
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/NoLoggingTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver;
+
+import java.lang.reflect.Method;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test a class that needs no conversion at all.
+ */
+public class NoLoggingTest extends AbstractConversionHandlerTest {
+
+    @BeforeAll
+    public static void setup() throws Exception {
+        transformClass("org/apache/logging/log4j/weaver/NoLoggingExample");
+    }
+
+    @Test
+    public void testGreet() throws Exception {
+        final Method greetMethod = convertedClass.getDeclaredMethod("greet");
+        final Object message = greetMethod.invoke(testObject);
+        assertThat(message).isEqualTo("Hello Log4j2!");
+    }
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandlerExample.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandlerExample.java
new file mode 100644
index 0000000..0bf9cbd
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandlerExample.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver.log4j2;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.logging.log4j.LogBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LogBuilderConversionHandlerExample {
+
+    public void testWithLocation() {
+        // We can't use verify: if `withLocation` is correctly remove, so is
+        // verify(...).withLocation()
+        final AtomicBoolean called = new AtomicBoolean();
+        final AtomicReference<StackTraceElement> locationRef = new AtomicReference<>();
+        final LogBuilder logBuilder = new LogBuilderMock(locationRef, called);
+        // We remove the call without parameters
+        logBuilder.withLocation().log();
+        assertThat(called).isFalse();
+
+        final StackTraceElement stackTraceElement = new StackTraceElement(
+                LogBuilderConversionHandlerExample.class.getName(), "specialMethod",
+                "LogBuilderConversionHandlerExample.java", 1024);
+        logBuilder.withLocation(stackTraceElement).log();
+        assertThat(locationRef).hasValue(stackTraceElement);
+    }
+
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandlerTest.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandlerTest.java
new file mode 100644
index 0000000..92059bd
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandlerTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver.log4j2;
+
+import org.apache.logging.log4j.weaver.AbstractConversionHandlerTest;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class LogBuilderConversionHandlerTest extends AbstractConversionHandlerTest {
+
+    @BeforeAll
+    public static void setup() throws Exception {
+        transformClass("org/apache/logging/log4j/weaver/log4j2/LogBuilderConversionHandlerExample");
+    }
+
+    @Test
+    public void testWithLocation() throws Exception {
+        convertedClass.getMethod("testWithLocation").invoke(testObject);
+    }
+
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderMock.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderMock.java
new file mode 100644
index 0000000..25e0b46
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LogBuilderMock.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver.log4j2;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.logging.log4j.LogBuilder;
+
+public final class LogBuilderMock implements LogBuilder {
+    private final AtomicReference<StackTraceElement> locationRef;
+    private final AtomicBoolean called;
+
+    public LogBuilderMock(AtomicReference<StackTraceElement> locationRef, AtomicBoolean called) {
+        this.locationRef = locationRef;
+        this.called = called;
+    }
+
+    @Override
+    public LogBuilder withLocation() {
+        called.set(true);
+        return LogBuilder.super.withLocation();
+    }
+
+    @Override
+    public LogBuilder withLocation(StackTraceElement location) {
+        locationRef.set(location);
+        return LogBuilder.super.withLocation(location);
+    }
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandlerExample.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandlerExample.java
new file mode 100644
index 0000000..eef7cc5
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandlerExample.java
@@ -0,0 +1,943 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver.log4j2;
+
+import java.util.List;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
+import org.apache.logging.log4j.message.EntryMessage;
+import org.apache.logging.log4j.message.ExitMessage;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.spi.AbstractLogger;
+import org.apache.logging.log4j.util.Supplier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class LoggerConversionHandlerExample {
+
+    private static final CharSequence CHAR_SEQUENCE = "(CharSequence)";
+    private static final Message MESSAGE = new SimpleMessage("(Message)");
+    private static final EntryMessage ENTRY_MESSAGE = new DefaultFlowMessageFactory().newEntryMessage(MESSAGE);
+    private static final String STRING = "(String)";
+    private static final Object OBJECT = "(Object)";
+    private static final Object P0 = "(p0)";
+    private static final Object P1 = "(p1)";
+    private static final Object P2 = "(p2)";
+    private static final Object P3 = "(p3)";
+    private static final Object P4 = "(p4)";
+    private static final Object P5 = "(p5)";
+    private static final Object P6 = "(p6)";
+    private static final Object P7 = "(p7)";
+    private static final Object P8 = "(p8)";
+    private static final Object P9 = "(p9)";
+    private static final Object[] PARRAY = { "(...)" };
+    private static final Supplier<?>[] SUPPLIERS = { () -> OBJECT };
+    private static final Throwable THROWABLE = new RuntimeException();
+    private static final Marker MARKER = MarkerManager.getMarker("MARKER");
+
+    private static final Logger logger = LogManager.getLogger();
+
+    private static final int referenceLine = 63;
+
+    public void testFatal(final ListAppender app) {
+        app.clear();
+        final String methodName = "testFatal";
+        int lineNumber = referenceLine + 5; // current line number
+        logger.fatal(CHAR_SEQUENCE);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.fatal(CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(() -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(() -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(() -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(() -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.fatal(MARKER, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testError(final ListAppender app) {
+        app.clear();
+        final String methodName = "testError";
+        int lineNumber = referenceLine + 103; // current line number
+        logger.error(CHAR_SEQUENCE);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.error(CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(() -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(() -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(() -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(() -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.error(MARKER, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testWarn(final ListAppender app) {
+        app.clear();
+        final String methodName = "testWarn";
+        int lineNumber = referenceLine + 201; // current line number
+        logger.warn(CHAR_SEQUENCE);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.warn(CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(() -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(() -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(() -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(() -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.warn(MARKER, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testInfo(final ListAppender app) {
+        app.clear();
+        final String methodName = "testInfo";
+        int lineNumber = referenceLine + 299; // current line number
+        logger.info(CHAR_SEQUENCE);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.info(CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(() -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(() -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(() -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(() -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.info(MARKER, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testDebug(final ListAppender app) {
+        app.clear();
+        final String methodName = "testDebug";
+        int lineNumber = referenceLine + 397; // current line number
+        logger.debug(CHAR_SEQUENCE);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.debug(CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(() -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(() -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(() -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(() -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.debug(MARKER, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testTrace(final ListAppender app) {
+        app.clear();
+        final String methodName = "testTrace";
+        int lineNumber = referenceLine + 495; // current line number
+        logger.trace(CHAR_SEQUENCE);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.trace(CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(() -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(() -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(() -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(() -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.trace(MARKER, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testLog(final ListAppender app) {
+        app.clear();
+        final String methodName = "testLog";
+        int lineNumber = referenceLine + 593; // current line number
+        logger.log(Level.INFO, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.log(Level.INFO, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, CHAR_SEQUENCE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, CHAR_SEQUENCE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2, P3);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2, P3, P4);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2, P3, P4, P5);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2, P3, P4, P5, P6);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, PARRAY);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, () -> MESSAGE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, () -> MESSAGE, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, () -> OBJECT);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, () -> OBJECT, THROWABLE);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.log(Level.INFO, MARKER, STRING, SUPPLIERS);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testFrames(final ListAppender app) {
+        app.clear();
+        final String methodName = "testFrames";
+        final int lineNumber = referenceLine + 691; // Current line
+        int i = 0;
+        while (i < 2) {
+            if (i < 1) {
+                logger.debug(OBJECT);
+                assertLocationEquals(methodName, lineNumber + 4, app);
+            } else {
+                logger.debug(OBJECT);
+                assertLocationEquals(methodName, lineNumber + 7, app);
+            }
+            logger.debug(OBJECT);
+            assertLocationEquals(methodName, lineNumber + 10, app);
+            i++;
+        }
+        switch (i) {
+            case 2:
+                logger.debug(OBJECT);
+                assertLocationEquals(methodName, lineNumber + 16, app);
+                break;
+        }
+        for (; i >= 0; i--) {
+            logger.debug(OBJECT);
+            assertLocationEquals(methodName, lineNumber + 21, app);
+        }
+    }
+
+    public void testPrintf(final ListAppender app) {
+        app.clear();
+        final String methodName = "testPrintf";
+        int lineNumber = referenceLine + 720; // Current line
+        logger.printf(Level.INFO, "Hello %s.%s", "LoggerConversionHandlerExample", methodName);
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.printf(Level.INFO, MARKER, "Hello %s.%s", "LoggerConversionHandlerExample", methodName);
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    public void testLogBuilder(final ListAppender app) {
+        app.clear();
+        final String methodName = "testLogBuilder";
+        int lineNumber = referenceLine + 730; // Current line
+        logger.always().log();
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.atDebug().log();
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.atError().log();
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.atFatal().log();
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.atInfo().log();
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.atLevel(Level.INFO).log();
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.atTrace().log();
+        assertLocationEquals(methodName, lineNumber += 2, app);
+        logger.atWarn().log();
+        assertLocationEquals(methodName, lineNumber += 2, app);
+    }
+
+    /**
+     * Tests the method calls that should be not modified.
+     */
+    public void testPassthrough(final ListAppender app) {
+        assertTrue(logger.isDebugEnabled());
+        assertTrue(logger.isDebugEnabled(MARKER));
+        assertTrue(logger.isEnabled(Level.INFO));
+        assertTrue(logger.isEnabled(Level.INFO, MARKER));
+        assertTrue(logger.isErrorEnabled());
+        assertTrue(logger.isErrorEnabled(MARKER));
+        assertTrue(logger.isFatalEnabled());
+        assertTrue(logger.isFatalEnabled(MARKER));
+        assertTrue(logger.isInfoEnabled());
+        assertTrue(logger.isInfoEnabled(MARKER));
+        assertTrue(logger.isTraceEnabled());
+        assertTrue(logger.isTraceEnabled(MARKER));
+        assertTrue(logger.isWarnEnabled());
+        assertTrue(logger.isWarnEnabled(MARKER));
+        app.clear();
+        logger.logMessage(Level.INFO, MARKER, LoggerConversionHandlerExample.class.getName(), null, MESSAGE, THROWABLE);
+        assertThat(app.getEvents()).hasSize(1);
+    }
+
+    public void testCatchingThrowing(final ListAppender app) {
+        app.clear();
+        final String methodName = "testCatchingThrowing";
+        int lineNumber = referenceLine + 775; // Current line
+        logger.catching(THROWABLE);
+        assertThat(app.getEvents()).allMatch(event -> AbstractLogger.CATCHING_MARKER.equals(event.getMarker()));
+        assertLocationEquals(methodName, ++lineNumber, app);
+        logger.catching(Level.INFO, THROWABLE);
+        assertThat(app.getEvents()).allMatch(event -> AbstractLogger.CATCHING_MARKER.equals(event.getMarker()));
+        assertLocationEquals(methodName, lineNumber += 3, app);
+        assertThat(logger.throwing(THROWABLE)).isInstanceOf(RuntimeException.class);
+        assertThat(app.getEvents()).allMatch(event -> AbstractLogger.THROWING_MARKER.equals(event.getMarker()));
+        assertLocationEquals(methodName, lineNumber += 3, app);
+        assertThat(logger.throwing(THROWABLE)).isInstanceOf(RuntimeException.class);
+        assertThat(app.getEvents()).allMatch(event -> AbstractLogger.THROWING_MARKER.equals(event.getMarker()));
+        assertLocationEquals(methodName, lineNumber += 3, app);
+    }
+
+    public void testTraceEntry(final ListAppender app) {
+        app.clear();
+        final String methodName = "testTraceEntry";
+        int lineNumber = referenceLine + 793; // Current line
+        EntryMessage entryMessage = logger.traceEntry();
+        LogEvent event = assertLocationEquals(methodName, ++lineNumber, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.ENTRY_MARKER);
+        assertThat(entryMessage.getMessage()).isNull();
+
+        entryMessage = logger.traceEntry(MESSAGE);
+        event = assertLocationEquals(methodName, lineNumber += 5, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.ENTRY_MARKER);
+        assertThat(entryMessage.getMessage()).isEqualTo(MESSAGE);
+
+        entryMessage = logger.traceEntry("param1 = {}", PARRAY);
+        event = assertLocationEquals(methodName, lineNumber += 5, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.ENTRY_MARKER);
+        assertThat(entryMessage.getFormattedMessage()).isEqualTo("Enter param1 = (...)");
+
+        entryMessage = logger.traceEntry(() -> P0, () -> P1);
+        event = assertLocationEquals(methodName, lineNumber += 5, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.ENTRY_MARKER);
+        assertThat(entryMessage.getFormattedMessage()).isEqualTo("Enter params((p0), (p1))");
+
+        entryMessage = logger.traceEntry("param1 = {}, param2 = {}", () -> P0, () -> P1);
+        event = assertLocationEquals(methodName, lineNumber += 5, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.ENTRY_MARKER);
+        assertThat(entryMessage.getFormattedMessage()).isEqualTo("Enter param1 = (p0), param2 = (p1)");
+    }
+
+    public void testTraceExit(final ListAppender app) {
+        app.clear();
+        final String methodName = "testTraceExit";
+        int lineNumber = referenceLine + 823; // Current line
+        logger.traceExit();
+        LogEvent event = assertLocationEquals(methodName, ++lineNumber, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.EXIT_MARKER);
+        assertThat(event.getMessage()).isInstanceOf(ExitMessage.class);
+        assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Exit");
+
+        logger.traceExit(ENTRY_MESSAGE);
+        event = assertLocationEquals(methodName, lineNumber += 6, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.EXIT_MARKER);
+        assertThat(event.getMessage()).isInstanceOf(ExitMessage.class);
+        assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Exit (Message)");
+
+        Object result = logger.traceExit(OBJECT);
+        assertThat(result).isSameAs(OBJECT);
+        event = assertLocationEquals(methodName, lineNumber += 6, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.EXIT_MARKER);
+        assertThat(event.getMessage()).isInstanceOf(ExitMessage.class);
+        assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Exit with((Object))");
+
+        result = logger.traceExit("result = {}", OBJECT);
+        assertThat(result).isSameAs(OBJECT);
+        event = assertLocationEquals(methodName, lineNumber += 7, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.EXIT_MARKER);
+        assertThat(event.getMessage()).isInstanceOf(ExitMessage.class);
+        assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Exit result = (Object)");
+
+        result = logger.traceExit(MESSAGE, OBJECT);
+        assertThat(result).isSameAs(OBJECT);
+        event = assertLocationEquals(methodName, lineNumber += 7, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.EXIT_MARKER);
+        assertThat(event.getMessage()).isInstanceOf(ExitMessage.class);
+        assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Exit (Message): (Object)");
+
+        result = logger.traceExit(ENTRY_MESSAGE, OBJECT);
+        assertThat(result).isSameAs(OBJECT);
+        event = assertLocationEquals(methodName, lineNumber += 7, app);
+        assertThat(event.getMarker()).isEqualTo(AbstractLogger.EXIT_MARKER);
+        assertThat(event.getMessage()).isInstanceOf(ExitMessage.class);
+        assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Exit (Message): (Object)");
+    }
+
+    private static LogEvent assertLocationEquals(final String methodName, final int lineNumber,
+            final ListAppender app) {
+        final List<LogEvent> events = app.getEvents();
+        assertThat(events).hasSize(1);
+        final LogEvent event = events.get(0);
+        assertThat(event.isIncludeLocation()).isFalse();
+        assertThat(event.getSource()).isNotNull();
+        final StackTraceElement location = event.getSource();
+        assertThat(location.getClassName()).isEqualTo(LoggerConversionHandlerExample.class.getName());
+        assertThat(location.getMethodName()).isEqualTo(methodName);
+        assertThat(location.getFileName()).isEqualTo("LoggerConversionHandlerExample.java");
+        assertThat(location.getLineNumber()).isEqualTo(lineNumber);
+        app.clear();
+        return event;
+    }
+}
diff --git a/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandlerTest.java b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandlerTest.java
new file mode 100644
index 0000000..3e146bb
--- /dev/null
+++ b/log4j-weaver/src/test/java/org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandlerTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.weaver.log4j2;
+
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
+import org.apache.logging.log4j.core.test.junit.Named;
+import org.apache.logging.log4j.weaver.AbstractConversionHandlerTest;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@LoggerContextSource("log4j2-test.xml")
+public class LoggerConversionHandlerTest extends AbstractConversionHandlerTest {
+
+    private ListAppender appender;
+
+    @BeforeAll
+    public static void setup() throws Exception {
+        transformClass("org/apache/logging/log4j/weaver/log4j2/LoggerConversionHandlerExample");
+    }
+
+    @BeforeEach
+    public void setupAppender(final @Named("List") ListAppender appender) {
+        this.appender = appender;
+    }
+
+    static Stream<String> testLocationConverter() {
+        return Stream.of("testFatal", "testError", "testWarn", "testInfo", "testDebug", "testLog", "testFrames",
+                "testPrintf", "testLogBuilder", "testPassthrough", "testCatchingThrowing", "testTraceEntry",
+                "testTraceExit");
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    public void testLocationConverter(final String methodName) throws Exception {
+        convertedClass.getMethod(methodName, ListAppender.class).invoke(testObject, appender);
+    }
+
+}
diff --git a/log4j-weaver/src/test/resources/log4j2-test.xml b/log4j-weaver/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..3d3e84c
--- /dev/null
+++ b/log4j-weaver/src/test/resources/log4j2-test.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<Configuration status="off">
+  <Appenders>
+    <List name="List" />
+  </Appenders>
+  <Loggers>
+    <Root level="trace" includeLocation="false">
+      <AppenderRef ref="List" />
+    </Root>
+  </Loggers>
+</Configuration>
diff --git a/pom.xml b/pom.xml
index c228083..b1cb6e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -132,6 +132,8 @@
   <modules>
     <!-- the parent POM must come first: -->
     <module>log4j-transform-parent</module>
+    <module>log4j-transform-maven-plugin</module>
+    <module>log4j-weaver</module>
   </modules>
   <scm>
     <connection>scm:git:git@github.com:apache/logging-log4j-transform.git</connection>
@@ -168,7 +170,20 @@
     <sign-maven-plugin.version>1.0.1</sign-maven-plugin.version>
     <spotless-maven-plugin.version>2.29.0</spotless-maven-plugin.version>
   </properties>
-  <dependencyManagement/>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-transform-maven-plugin</artifactId>
+        <version>${revision}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-weaver</artifactId>
+        <version>${revision}</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
   <build>
     <pluginManagement>
       <plugins>
@@ -196,7 +211,6 @@
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>flatten-maven-plugin</artifactId>
-        <version>${flatten-maven-plugin.version}</version>
         <inherited>false</inherited>
         <executions>
           <execution>
@@ -244,9 +258,9 @@
           <pom>
             <sortPom>
               <expandEmptyElements>false</expandEmptyElements>
-              <keepBlankLines>false</keepBlankLines>
+              <keepBlankLines>true</keepBlankLines>
               <indentSchemaLocation>true</indentSchemaLocation>
-              <sortDependencies>artifactId,groupId</sortDependencies>
+              <sortDependencies>scope,artifactId,groupId</sortDependencies>
               <sortDependencyExclusions>artifactId,groupId</sortDependencyExclusions>
               <sortPlugins>artifactId,groupId</sortPlugins>
             </sortPom>
diff --git a/src/changelog/.0.1.x/LOG4J2-3638_Provide_Maven_plugin_to_inline_location.xml b/src/changelog/.0.1.x/LOG4J2-3638_Provide_Maven_plugin_to_inline_location.xml
new file mode 100644
index 0000000..d449168
--- /dev/null
+++ b/src/changelog/.0.1.x/LOG4J2-3638_Provide_Maven_plugin_to_inline_location.xml
@@ -0,0 +1,7 @@
+<entry type="added">
+  <issue id="LOG4J2-3638" link="https://issues.apache.org/jira/browse/LOG4J2-3638"/>
+  <author id="pkarwasz"/>
+  <description format="asciidoc">
+    Add bytecode transformation tool to provide location information without reflection.
+  </description>
+</entry>