Michael Rumpf
Michael Rumpf

Reputation: 371

Java Util Logging redirection to Log4j2 not working for Spring Boot applications

I'm trying to redirect all logging frameworks to Log4j2 in my Spring Boot application. It works fine for Java Commons Logging, SLF4J, and Log4j 1.x. Unfortunately the Java Util Logging (JUL) redirection does not work because java.util.logging.Logger is used by the Plexus Launcher at a time when the application classpath has not been fully assembled.

The Log4j2 documentation demands to set the VM parameter -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager or to call the System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") method to make the redirection work. Neither of them work:

The call to System.setProperty() gets ignored because the Java Util Logging framework is already initialized. With the VM parameter the following exception occurs:

$ mvn spring-boot:run -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager

Could not load Logmanager "org.apache.logging.log4j.jul.LogManager"
java.lang.ClassNotFoundException: org.apache.logging.log4j.jul.LogManager
    at org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy.loadClass(SelfFirstStrategy.java:50)
    at org.codehaus.plexus.classworlds.realm.ClassRealm.unsynchronizedLoadClass(ClassRealm.java:271)
    at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:247)
    at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:239)
    at java.util.logging.LogManager$1.run(LogManager.java:195)
    at java.util.logging.LogManager$1.run(LogManager.java:181)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.util.logging.LogManager.<clinit>(LogManager.java:181)
    at java.util.logging.Logger.demandLogger(Logger.java:448)
    at java.util.logging.Logger.getLogger(Logger.java:502)
    at com.google.inject.internal.util.Stopwatch.<clinit>(Stopwatch.java:27)
    at com.google.inject.internal.InternalInjectorCreator.<init>(InternalInjectorCreator.java:61)
    at com.google.inject.Guice.createInjector(Guice.java:96)
    at com.google.inject.Guice.createInjector(Guice.java:73)
    at com.google.inject.Guice.createInjector(Guice.java:62)
    at org.codehaus.plexus.DefaultPlexusContainer.addPlexusInjector(DefaultPlexusContainer.java:481)
    at org.codehaus.plexus.DefaultPlexusContainer.<init>(DefaultPlexusContainer.java:206)
    at org.apache.maven.cli.MavenCli.container(MavenCli.java:542)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:279)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:197)
    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:497)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)

Digging into the code shows that the initialization is caused by the Guice library, which uses JUL internally. In this case it is just a call to com.google.inject.internal.util.Stopwatch.<clinit>(Stopwatch.java:27).

So the question is, does somone have an idea on how to resolve this?

Update 2015-08-23: The suggestion from below to add the configuration property <fork>true</fork> works for running the application via the Spring boot Maven plugin mvn spring-boot:run.

Unfortunately it does not work when running the application via the command line java -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -jar myapp.jar. In this case the following exception occurs:

Could not load Logmanager "org.apache.logging.log4j.jul.LogManager"
java.lang.ClassNotFoundException: org.apache.logging.log4j.jul.LogManager
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.util.logging.LogManager$1.run(LogManager.java:195)
    at java.util.logging.LogManager$1.run(LogManager.java:181)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.util.logging.LogManager.<clinit>(LogManager.java:181)
    at java.util.logging.Logger.demandLogger(Logger.java:448)
    at java.util.logging.Logger.getLogger(Logger.java:502)
    at org.springframework.boot.loader.Launcher.<init>(Launcher.java:43)
    at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:48)
    at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:45)
    at org.springframework.boot.loader.JarLauncher.<init>(JarLauncher.java:30)
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:45)

So, unfortunately the Spring Boot Launcher is also using Java Util Logging and by that preventing JUL redirection with Log4j2.

I looked at the source-code of the class org.springframework.boot.loader.Launcher and its child classes. The issue is caused by the declaration of the static logger variable in the Launcher class. This logger seems not to be used by any of the Launcher classes so it could easily be removed. The question is whether the dependency to JUL could be removed completely to fix the JUL redirection issue? Maybe by using stderr for Launcher diagnostics, instead of JUL?

Upvotes: 4

Views: 4800

Answers (1)

Andy Wilkinson
Andy Wilkinson

Reputation: 116091

Your current approach means that both Maven and your application will try to use the custom log manager. Maven attempting to use it is causing the exception that you are seeing.

You could configure Spring Boot's Maven plugin in your pom.xml to fork a separate JVM for your application:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
      <fork>true</fork>
    </configuration>
  </plugin>

And then use System.setProperty in your application's main method. Forking a separate JVM will prevent Maven's own logging from messing things up before your configuration takes effect.

Upvotes: 3

Related Questions