Repository: netty/netty
Issue: Log4J2LoggerFactory depends on a log4j2 version which does not work on Java 6

### Expected behavior
Netty logging works via log4j2 while on Java 6

### Actual behavior
Log4j2 2.4+ depends on Java 7. Netty depends on log4j2 methods which are present only in newer versions. When older log4j2 version is on a classpath then Netty fails to initialize itself due `AbstractMethodError` while logging. 

### Steps to reproduce
Have Netty 4.1.27 and log4j2 on a classpath and run `InternalLoggerFactory.getInstance("foo").debug("bar", new Object));`

### Minimal yet complete reproducer code (or URL to code)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>info.jerrinot</groupId>
    <artifactId>netty-logging-issue-reproducer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-common</artifactId>
            <version>4.1.27.Final</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.3</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>
```

```java
package info.jerrinot.nettyloggingissue;

import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.Test;

public class ReproducerTest {

    @Test
    public void testNettyLogging() {
        InternalLoggerFactory.getInstance("foo").debug("bar", new Object());
    }
}
```

### Netty version
4.1.27

### JVM version (e.g. `java -version`)
irrelevant
### OS version (e.g. `uname -a`)
irrelevant

Issue comments:
fwiw - we use OpenSSL in some integration tests. They have to run on Java 6. I end-up with implementing this nasty hack (test-code only):
```java
    private static void patchOpenSslLogging() {
        InternalLoggerFactory originFactory = InternalLoggerFactory.getDefaultFactory();
        if (!originFactory.getClass().getName().equals("io.netty.util.internal.logging.Log4J2LoggerFactory")) {
            // it's already patched or netty does not use log4j2 at all
            return;
        }

        // Netty created Log4J2LoggerFactory this implies we have log4j2 on a classpath
        // let's check what version is this
        try {
            ExtendedLoggerWrapper.class.getMethod("debug", String.class, Object.class);
            // apparently we are using a new log4j2 version! This means we live in the future
            // and no longer supporting Java 6. Hence this whole netty logging patching should
            // be removed!
            throw new AssertionError("You managed to upgrade log4j2, congratulations!"
                    + " Now let's removing the ugly netty logging patching!");
        } catch (NoSuchMethodException e) {
            // ok, this is an old version of log4j2. let's do some ugly patching

            // reasoning:
            // log4j2 2.3 is the last version supporting Java 6. However it's incompatible
            // with Netty - some methods are missing and this results in AbstractMethodError
            // so let's patch the Netty logging to use our wrapper which implements the missing
            // methods on its own.
            InternalLoggerFactory wrappedFactory = new PatchedLog4j2NettyLoggerFactory(originFactory);
            InternalLoggerFactory.setDefaultFactory(wrappedFactory);
        }
    }
```

```java

/**
 * Patched log4j2 netty logger factory.
 *
 * Reasoning: Netty requires Java 6, but its Log4j2 logger implementation
 * requires a version of Log4j2 which does not work on Java 6.
 *
 * Netty with old Log4j2 version throws AbstractMethodError. This factory produces
 * loggers which bypass the missing methods.
 *
 */
public final class PatchedLog4j2NettyLoggerFactory extends InternalLoggerFactory {
    private final Log4J2LoggerFactory delegate;

    public PatchedLog4j2NettyLoggerFactory(InternalLoggerFactory delegate) {
        this.delegate = (Log4J2LoggerFactory) delegate;
    }

    @Override
    protected InternalLogger newInstance(String s) {
        return new DelegatingLogger(delegate.newInstance(s));
    }

    private static class DelegatingLogger implements InternalLogger {
        private final InternalLogger delegate;

        private DelegatingLogger(InternalLogger delegate) {
            this.delegate = delegate;
        }

        @Override
        public String name() {
            return delegate.name();
        }

        @Override
        public boolean isTraceEnabled() {
            return delegate.isTraceEnabled();
        }

        @Override
        public void trace(String s) {
            delegate.trace(s);
        }

        @Override
        public void trace(String s, Object o) {
            delegate.trace(s, new Object[]{o});
        }

        @Override
        public void trace(String s, Object o, Object o1) {
            delegate.trace(s, new Object[]{o, o1});
        }

        @Override
        public void trace(String s, Object... objects) {
            delegate.trace(s, objects);
        }

        @Override
        public void trace(String s, Throwable throwable) {
            delegate.trace(s, throwable);
        }

        @Override
        public void trace(Throwable throwable) {
            delegate.trace(throwable);
        }

        @Override
        public boolean isDebugEnabled() {
            return delegate.isDebugEnabled();
        }

        @Override
        public void debug(String s) {
            delegate.debug(s);
        }

        @Override
        public void debug(String s, Object o) {
            delegate.debug(s, new Object[]{o});
        }

        @Override
        public void debug(String s, Object o, Object o1) {
            delegate.debug(s, new Object[]{o, o1});
        }

        @Override
        public void debug(String s, Object... objects) {
            delegate.debug(s, objects);
        }

        @Override
        public void debug(String s, Throwable throwable) {
            delegate.debug(s, throwable);
        }

        @Override
        public void debug(Throwable throwable) {
            delegate.debug(throwable);
        }

        @Override
        public boolean isInfoEnabled() {
            return delegate.isInfoEnabled();
        }

        @Override
        public void info(String s) {
            delegate.info(s);
        }

        @Override
        public void info(String s, Object o) {
            delegate.info(s, new Object[]{o});
        }

        @Override
        public void info(String s, Object o, Object o1) {
            delegate.info(s, new Object[]{o, o1});
        }

        @Override
        public void info(String s, Object... objects) {
            delegate.info(s, objects);
        }

        @Override
        public void info(String s, Throwable throwable) {
            delegate.info(s, throwable);
        }

        @Override
        public void info(Throwable throwable) {
            delegate.info(throwable);
        }

        @Override
        public boolean isWarnEnabled() {
            return delegate.isWarnEnabled();
        }

        @Override
        public void warn(String s) {
            delegate.warn(s);
        }

        @Override
        public void warn(String s, Object o) {
            delegate.warn(s, new Object[]{o});
        }

        @Override
        public void warn(String s, Object... objects) {
            delegate.warn(s, objects);
        }

        @Override
        public void warn(String s, Object o, Object o1) {
            delegate.warn(s, new Object[]{o, o1});
        }

        @Override
        public void warn(String s, Throwable throwable) {
            delegate.warn(s, throwable);
        }

        @Override
        public void warn(Throwable throwable) {
            delegate.warn(throwable);
        }

        @Override
        public boolean isErrorEnabled() {
            return delegate.isErrorEnabled();
        }

        @Override
        public void error(String s) {
            delegate.error(s);
        }

        @Override
        public void error(String s, Object o) {
            delegate.error(s, new Object[]{o});
        }

        @Override
        public void error(String s, Object o, Object o1) {
            delegate.error(s, new Object[]{o, o1});
        }

        @Override
        public void error(String s, Object... objects) {
            delegate.error(s, objects);
        }

        @Override
        public void error(String s, Throwable throwable) {
            delegate.error(s, throwable);
        }

        @Override
        public void error(Throwable throwable) {
            delegate.error(throwable);
        }

        @Override
        public boolean isEnabled(InternalLogLevel internalLogLevel) {
            return delegate.isEnabled(internalLogLevel);
        }

        @Override
        public void log(InternalLogLevel internalLogLevel, String s) {
            delegate.log(internalLogLevel, s);
        }

        @Override
        public void log(InternalLogLevel internalLogLevel, String s, Object o) {
            delegate.log(internalLogLevel, s, o);
        }

        @Override
        public void log(InternalLogLevel internalLogLevel, String s, Object o, Object o1) {
            delegate.log(internalLogLevel, s, o, o1);
        }

        @Override
        public void log(InternalLogLevel internalLogLevel, String s, Object... objects) {
            delegate.log(internalLogLevel, s, objects);
        }

        @Override
        public void log(InternalLogLevel internalLogLevel, String s, Throwable throwable) {
            delegate.log(internalLogLevel, s, throwable);
        }

        @Override
        public void log(InternalLogLevel internalLogLevel, Throwable throwable) {
            delegate.log(internalLogLevel, throwable);
        }
    }
```

Basically it delegates methods `debug(String msg, Obect o)` into `debug(String msg, Object[] o)`. This creates some extra litter but that's fine for functional-only tests. 
@jerrinot can you show me the stack trace please ?
hi @normanmaurer, here is it:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>info.jerrinot</groupId>
    <artifactId>netty-logging-issue-reproducer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <netty.version>4.1.27.Final</netty.version>
        <netty-tcnative.version>2.0.12.Final</netty-tcnative.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-common</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-handler</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-tcnative-boringssl-static</artifactId>
            <version>${netty-tcnative.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.3</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>
```


```java
    @Test
    public void testNettyLoggingStacktrace() throws Throwable {
        Throwable cause = OpenSsl.unavailabilityCause();
        if (cause != null) {
            throw cause;
        }
    }
```

```
java.lang.AbstractMethodError: io.netty.util.internal.logging.Log4J2Logger.debug(Ljava/lang/String;Ljava/lang/Object;)V

	at io.netty.util.internal.PlatformDependent0.explicitNoUnsafeCause0(PlatformDependent0.java:376)
	at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:42)
	at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:208)
	at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:79)
	at io.netty.handler.ssl.OpenSsl.loadTcNative(OpenSsl.java:424)
	at io.netty.handler.ssl.OpenSsl.<clinit>(OpenSsl.java:97)
	at info.jerrinot.nettyloggingissue.ReproducerTest.testNettyLoggingStacktrace(ReproducerTest.java:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
```

I was quite confused by the stacktrace, it took me a while to realize what is going on: `io.netty.util.internal.logging.Log4J2Logger` is a subclass(!) of `org.apache.logging.log4j.spi.ExtendedLoggerWrapper`.  

The Netty logging abstraction (`io.netty.util.internal.logging.InternalLogger`) has methods such as `debug(String format, Object arg)`. This method happens to be implemented by `o.a.l.l.s.ExtendedLoggerWrapper` (or its superclass), but only in newer log4j2 versions. When an old version of log4j2 is available in runtime then calling `internalLogger.debug(string, o)` throws the `AbstractMethodError`. 
@jerrinot could you validate https://github.com/netty/netty/pull/8239 ?
@jerrinot https://github.com/netty/netty/pull/8240 this one is better.
hi @normanmaurer, I tried #8240 and I can confirm the test passes and Netty fallbacks to j.u.l. 

This effectively means it's impossible to use Netty with Log4j2 on Java 6. That's not great for embedded use-cases. From this perspective the #8239 is nicer. 

I understand if you do not want to to pollute log4j2 logger with the branching for the sake of Java 6. Perhaps it could be a separate logger class. Think of `LegacyLog4J2Logger`. Would you accept a PR with this? 
@ejona86 WDYT ?
> This effectively means it's impossible to use Netty with Log4j2 on Java 6.

Yes, but that's never worked. I took this issue to be a bug report that Netty breaks (which it shouldn't). But it sounds like you're thinking of this more of a feature request to support log4j2 on Java 6?

> I understand if you do not want to to pollute log4j2 logger with the branching for the sake of Java 6.

I was not as concerned at the branching, but with the "why are we investing this much?" It seemed weird to go so far out of our way to add a new feature to Netty to support a 3-year-dead release (2.4 came out 3 years ago)? I'd understand more if log4j2 still supported Java 6 in some branch, but it doesn't seem to. I'd also understand more if log4j2+Netty+Java 6 was previously supported, but from what I can tell it wasn't. Also, log4j2 has actually received important changes which reduce garbage which make sense for Netty to use. I sort of feel like "log4j2 is not supporting Java 6" is the bug.

That said, the large changes to Log4J2Logger are also present in #8240 so I may have been misattributing some of what is necessary to support older Log4J2 versions.

Pull request review comments:
mismatch*

Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/internal/logging/Log4J2Logger.java b/common/src/main/java/io/netty/util/internal/logging/Log4J2Logger.java
index 36f28083753..5c3593f203a 100644
--- a/common/src/main/java/io/netty/util/internal/logging/Log4J2Logger.java
+++ b/common/src/main/java/io/netty/util/internal/logging/Log4J2Logger.java
@@ -21,14 +21,42 @@
 import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;
 
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
 import static io.netty.util.internal.logging.AbstractInternalLogger.EXCEPTION_MESSAGE;
 
 class Log4J2Logger extends ExtendedLoggerWrapper implements InternalLogger {
 
     private static final long serialVersionUID = 5485418394879791397L;
+    private static final boolean VARARGS_ONLY;
+
+    static {
+        // Older Log4J2 versions have only log methods that takes the format + varargs. So we should not use
+        // Log4J2 if the version is too old.
+        // See https://github.com/netty/netty/issues/8217
+        VARARGS_ONLY = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+            @Override
+            public Boolean run() {
+                try {
+                    Logger.class.getMethod("debug", String.class, Object.class);
+                    return false;
+                } catch (NoSuchMethodException ignore) {
+                    // Log4J2 version too old.
+                    return true;
+                } catch (SecurityException ignore) {
+                    // We could not detect the version so we will use Log4J2 if its on the classpath.
+                    return false;
+                }
+            }
+        });
+    }
 
     Log4J2Logger(Logger logger) {
         super((ExtendedLogger) logger, logger.getName(), logger.getMessageFactory());
+        if (VARARGS_ONLY) {
+            throw new UnsupportedOperationException("Log4J2 version mismatch");
+        }
     }
 
     @Override


Test patch: