Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix getLogger() incompatibility in log4j-1.2-api #3030

Merged
merged 4 commits into from
Oct 1, 2024

Conversation

taichi
Copy link
Contributor

@taichi taichi commented Sep 27, 2024

This Pull Request addresses a type resolution issue that occurs when compiling legacy code using log4j-1.2-api with OpenJDK.
The problem arises from a subtle difference in method signatures between the getLogger methods defined in org.apache.log4j.Logger provided by log4j-1.2-api and the original log4j library.

Root Cause

The discrepancy lies in the method signatures of getLogger:

  1. log4j-1.2-api defines it as: Source
public static Logger getLogger(final Class<?> clazz)
  1. Original log4j defines it as: Source
public static Logger getLogger(final Class clazz)

This difference becomes problematic when dealing with legacy code that includes custom Logger classes extending the original org.apache.log4j.Logger.

Example of Affected Legacy Code

Consider a custom MyLogger class:

public class MyLogger extends org.apache.log4j.Logger {
  protected MyLogger(String name) {
    super(name);
  }
  public static MyLogger getLogger(Class clazz) {
    return (MyLogger) Logger.getLogger(clazz.getName());
  }
}

Used in code like this:

public class MyObject {
  static final MyLogger logger = MyLogger.getLogger(MyObject.class);
}

When compiling with Java 21, the following error occurs:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project log4j-app: Compilation failure
[ERROR] /C:/work/java/xxxx/zzzzz/src/main/java/zzzzz/foo/bar/MyObject.java:[5,58] incompatible types: org.apache.log4j.Logger cannot be converted to zzzzz.foo.bar.MyLogger

This error occurs because the getLogger method in MyLogger lacks a type parameter, causing the compiler to select the getLogger method from org.apache.log4j.Logger instead.

Verification

To confirm this, modifying the MyLogger class as follows resolves the compilation issue:

public class MyLogger extends org.apache.log4j.Logger {
  protected MyLogger(String name) {
    super(name);
  }
  public static MyLogger getLogger(Class<?> clazz) {
    return (MyLogger) Logger.getLogger(clazz.getName());
  }
}

This Pull Request implements a solution to address this type resolution problem, ensuring compatibility between legacy code using custom Logger extensions and the log4j-1.2-api library when compiling with OpenJDK.

Note

While we can rebuild the source code in our project, we cannot modify the existing legacy code itself.

@vy
Copy link
Member

vy commented Sep 30, 2024

@taichi, can you help me to understand the "working legacy code" first, please?

I created a new Maven project such that

pom.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>org.example</groupId>
  <artifactId>logging-log4j1</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
  </dependencies>

</project>

src/main/java/foo/MyLogger.java:

package foo;

import org.apache.log4j.Logger;

public class MyLogger extends Logger {
    protected MyLogger(String name) {
        super(name);
    }
    public static MyLogger getLogger(Class clazz) {
        return (MyLogger) getLogger(clazz.getName());
    }
}

And tried to use it in another Maven project such that

pom.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>org.example</groupId>
  <artifactId>logging-log4j1-usage</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.example</groupId>
      <artifactId>logging-log4j1</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>

</project>

src/main/java/foo/MyObject.java:

package foo;

public class MyObject {
    public static final MyLogger LOGGER = MyLogger.getLogger(MyObject.class);
    public static void main(String[] args) {
        LOGGER.info("foo");
    }
}

Though running MyObject fails as follows:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.ClassCastException: org.apache.log4j.Logger cannot be cast to foo.MyLogger
	at foo.MyLogger.getLogger(MyLogger.java:10)
	at foo.MyObject.<clinit>(MyObject.java:4)

(Using Azul Zulu 8 version 1.8.0_392.)

Hence, I am not able to reproduce what you claimed it used to work when log4j:log4j artifact is used. Could you provide me a minimal example like the one I shared above, please?

@vy vy self-assigned this Sep 30, 2024
@vy vy added api Affects the public API legacy-1.2-bridge waiting-for-user More information is needed from the user labels Sep 30, 2024
@taichi
Copy link
Contributor Author

taichi commented Oct 1, 2024

Thank you for your reply. My sample code was intended to focus solely on the compilation errors.
The executable legacy code is as follows:

import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggerFactory;

public class MyLogger extends Logger {
	
	static final LoggerFactory factory = new LoggerFactory() {
		
		@Override
		public Logger makeNewLoggerInstance(String name) {
			return new MyLogger(name);
		}
	};
	
	protected MyLogger(String name) {
		super(name);
	}
	
	public static MyLogger getLogger(@SuppressWarnings("rawtypes") final Class clazz) {
		return (MyLogger) Logger.getLogger(clazz.getName(), factory);
	}
}

Copy link

github-actions bot commented Oct 1, 2024

Job Requested goals Build Tool Version Build Outcome Build Scan®
build-macos-latest clean install 3.9.8 Build Scan PUBLISHED
build-ubuntu-latest clean install 3.9.8 Build Scan PUBLISHED
build-windows-latest clean install 3.9.8 Build Scan PUBLISHED
Generated by gradle/develocity-actions

@vy vy added the bug Incorrect, unexpected, or unintended behavior of existing code label Oct 1, 2024
@vy
Copy link
Member

vy commented Oct 1, 2024

@taichi, got it.

Is the change set complete?

I will accept this fix. Before merging your PR, could you verify that this is the only incompatibility you need to be fixed?

Further fixes

There are several such incompatibilities between log4j:log4j and org.apache.logging.log4j:log4j-1.2-api. You can view this yourself by running

./mvnw verify \
  -pl :log4j-1.2-api \
  -DskipTests -Dcyclonedx.skip -Dspotbugs.skip -Dspotless.skip -Denforce.skip -Dbnd.skip \
  -Dbnd.baseline.base.coordinates=log4j:log4j:1.2.17 \
  -Dbnd.baseline.full.report

and seeing the report dumped to the log4j-1.2-api/target/baseline folder1. Would you mind skimming through it and fixing other major issues that you spot, please?

1 See the bnd-baseline-maven-plugin website for details.

@taichi
Copy link
Contributor Author

taichi commented Oct 1, 2024

Thank you for your thorough review and the detailed instructions for detecting additional changes.
I appreciate your guidance in this matter.

I can confirm that all necessary changes have been completed in this pull request.
Regarding the command you provided to check for incompatibilities, I encountered an error when attempting to execute it as written. However, I was able to successfully run a slightly modified version of the command:

./mvnw verify \
  -pl :log4j-1.2-api \
  -DskipTests -Dcyclonedx.skip -Dspotbugs.skip -Dspotless.skip -Denforce.skip \
  -Dbnd.baseline.base.coordinates=log4j:log4j:1.2.17 \
  -Dbnd.baseline.full.report

After reviewing the output, I can assure you that there are no other major incompatibilities that require addressing at this time.

If you have any further questions or concerns, please don't hesitate to let me know. I'm ready to make any additional adjustments if needed.

@ppkarwasz
Copy link
Contributor

The breaking changes between log4j:log4j and log4j-1.2-api are actually huge. A small excerpt, where I only kept the breaking changes:

  Name                                               Type       Delta      New        Old        Suggest    If Prov.
  org.apache.log4j                                   PACKAGE    MAJOR      2.20.2     1.2.17     ok         -
  MAJOR                PACKAGE    org.apache.log4j
    REMOVED            CLASS      org.apache.log4j.AsyncAppender
    MAJOR              CLASS      org.apache.log4j.Category
      REMOVED          FIELD      resourceBundle
        REMOVED        ACCESS     protected
        REMOVED        RETURN     java.util.ResourceBundle
      REMOVED          METHOD     getResourceBundleString(java.lang.String)
        REMOVED        ACCESS     protected
        REMOVED        RETURN     java.lang.String
    REMOVED            CLASS      org.apache.log4j.DailyRollingFileAppender
    REMOVED            CLASS      org.apache.log4j.EnhancedPatternLayout
    REMOVED            CLASS      org.apache.log4j.EnhancedThrowableRenderer
    REMOVED            CLASS      org.apache.log4j.HTMLLayout
    MAJOR              CLASS      org.apache.log4j.Layout
      REMOVED          IMPLEMENTS org.apache.log4j.spi.OptionHandler
      REMOVED          METHOD     activateOptions()
        REMOVED        ACCESS     abstract
        REMOVED        RETURN     void
    REMOVED            CLASS      org.apache.log4j.LogMF
    MAJOR              CLASS      org.apache.log4j.LogManager
      ADDED            ACCESS     final
    REMOVED            CLASS      org.apache.log4j.LogSF
    REMOVED            CLASS      org.apache.log4j.LogXF
    MAJOR              CLASS      org.apache.log4j.Logger
      REMOVED          FIELD      resourceBundle
        REMOVED        ACCESS     protected
        REMOVED        RETURN     java.util.ResourceBundle
      REMOVED          METHOD     getResourceBundleString(java.lang.String)
        REMOVED        ACCESS     protected
        REMOVED        RETURN     java.lang.String
    MAJOR              CLASS      org.apache.log4j.MDC
      ADDED            ACCESS     final
    MAJOR              CLASS      org.apache.log4j.NDC
      ADDED            ACCESS     final
    MAJOR              CLASS      org.apache.log4j.PatternLayout
      REMOVED          IMPLEMENTS org.apache.log4j.spi.OptionHandler
    MAJOR              CLASS      org.apache.log4j.SimpleLayout
      REMOVED          IMPLEMENTS org.apache.log4j.spi.OptionHandler
      REMOVED          METHOD     activateOptions()
        REMOVED        RETURN     void
    REMOVED            CLASS      org.apache.log4j.TTCCLayout
  org.apache.log4j.config                            PACKAGE    MAJOR      2.20.1     1.2.17     ok         -
  MAJOR                PACKAGE    org.apache.log4j.config
    REMOVED            CLASS      org.apache.log4j.config.PropertyGetter
    REMOVED            INTERFACE  org.apache.log4j.config.PropertyGetter$PropertyCallback
  org.apache.log4j.helpers                           PACKAGE    MAJOR      2.20.2     1.2.17     ok         -
  MAJOR                PACKAGE    org.apache.log4j.helpers
    REMOVED            CLASS      org.apache.log4j.helpers.MDCKeySetExtractor
    MAJOR              CLASS      org.apache.log4j.helpers.NullEnumeration
      ADDED            ACCESS     final
    REMOVED            CLASS      org.apache.log4j.helpers.OnlyOnceErrorHandler
    REMOVED            CLASS      org.apache.log4j.helpers.SyslogQuietWriter
    REMOVED            CLASS      org.apache.log4j.helpers.SyslogWriter
    REMOVED            CLASS      org.apache.log4j.helpers.ThreadLocalMap

I can not find any reference to the change in Logger, but that might be a limitation in BND Baseline, so we might not be able to catch source compatibility problems with BND Baseline.

I am +1 on merging this PR, although I am surprised someone still uses Log4j 1 directly in their source code in 2024.
Logging APIs that make the code independent from the logging implementation used have been available since at least 2005 (commons-logging). We have a section of our migration guide (see Log4j 1 API Migration) that describes how to migrate from using o.a.l.Logger in the source code and is complete with an OpenRewrite conversion rule.

@taichi, what features does MyLogger have that warrant the usage of a custom logger? Custom loggers almost certainly suffer from the wrong caller information in the log file (each log event appears to originate from MyLogger).

@taichi
Copy link
Contributor Author

taichi commented Oct 1, 2024

@ppkarwasz Your observation is indeed valid and well-reasoned.

Firstly, I would like to clarify that caller information is not being recorded in our logs at all, so any potential inaccuracies in this regard do not pose an issue.
The primary purpose of our Custom Logger is to uniformly populate the Mapped Diagnostic Context (MDC) with specific information. While I personally concur that this alone may not justify the use of a Custom Logger, it is the current implementation that we have in place and it continues to function as intended.

I must emphasize that my primary objective is to successfully migrate this legacy codebase, originally written circa 2007, to a modern environment while maintaining its correct functionality. It is anticipated that over the next few years, the code utilizing this Custom Logger will be gradually phased out. However, at this juncture, our capacity for modification is limited to partial replacement using the log4j-1.2-api.

I appreciate your insights and would be happy to discuss any further questions or concerns you may have regarding this approach.

@vy vy removed the waiting-for-user More information is needed from the user label Oct 1, 2024
@ppkarwasz
Copy link
Contributor

Firstly, I would like to clarify that caller information is not being recorded in our logs at all, so any potential inaccuracies in this regard do not pose an issue. The primary purpose of our Custom Logger is to uniformly populate the Mapped Diagnostic Context (MDC) with specific information. While I personally concur that this alone may not justify the use of a Custom Logger, it is the current implementation that we have in place and it continues to function as intended.

If all it does it is add custom context data, you might consider implementing a custom ContextDataProvider.

If the context data is specific to a particular logger, please take a look at the discussion in #2214 and apache/logging-log4j-kotlin#71. We plan to introduce some new features in the Log4j API in the near future.

@vy vy merged commit 125fbb9 into apache:2.x Oct 1, 2024
9 checks passed
@vy vy changed the title Address Type Resolution Issues in Legacy Migration Using log4j-1.2-api with OpenJDK Compilation Fix getLogger() incompatibility in log4j-1.2-api Oct 1, 2024
@vy vy added this to the 2.25.0 milestone Oct 1, 2024
@taichi
Copy link
Contributor Author

taichi commented Oct 1, 2024

Thank you for guiding me to these discussions. As we have only recently begun using log4j2, this information is particularly valuable and insightful.

Based on my current understanding, I believe it may be possible to effectively integrate a ContextDataProvider with either JAX-RS ContainerRequestFilters or CDI Interceptors to facilitate the population of ContextData. This approach could potentially offer a more elegant solution to our current implementation.

@taichi taichi deleted the taichi-patch-1 branch October 2, 2024 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Affects the public API bug Incorrect, unexpected, or unintended behavior of existing code legacy-1.2-bridge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants