JUL Adapter not working for Jersey

2019-03-27 08:25发布

问题:

I am trying to use JUL Adapter to delegate Java Util Logging to Log4j2. More precisely, any third-party library that use JUL to generate logs, should be delegated to Log4j2.

As a simple exercise, I created a standalone application that uses a library (I created this library for testing purposes, it generates logs using JUL) to test the JUL Adapter. When I change the log manager as described here I can see the effects. And it works fine.

Hers's the code:

import org.apache.logging.log4j.LogManager;
import com.ah.loggen.LogGenerator;

public class TestLogging {

    static {
        System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
    }

    private static final org.apache.logging.log4j.Logger LOG4J = LogManager.getLogger();

    public static void main(String[] args) {
        System.out.println("Java Util Logging");
        LogGenerator.generateError("This is an error message.");
        LogGenerator.generateInfo("This is an info message.");
        LogGenerator.generateWarning("This is a warning message.");
        System.out.println("LOG4J");
        LOG4J.info("[LOG4J] This is an info message.");
        LOG4J.error("[LOG4J] This is an error message.");
        LOG4J.warn("[LOG4J] This is a warning message.");
    }
}

Dependencies required:

dependencies {
    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0'
    compile group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '2.10.0'

    compile files('lib/loggen.jar')

    testCompile 'junit:junit:4.12'
}

However, I cannot get this to work on a java web app that uses Jersey. Jersey uses JUL, and I'm trying to bridge it with Log4j2.

Here's the build.gradle file:

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse-wtp'
apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin'

repositories {
    mavenCentral()
}

task wrapper(type: Wrapper) {
    gradleVersion = '4.4.1'
}

dependencies {
    compile "javax.ws.rs:javax.ws.rs-api:2.1"
    compile "org.glassfish.jersey.core:jersey-server:2.22.1"
    compile "org.glassfish.jersey.containers:jersey-container-servlet:2.22.1"
    compile "org.glassfish.jersey.media:jersey-media-json-jackson:2.22.1" 
    providedCompile "javax.servlet:javax.servlet-api:3.1.0" 

    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.8'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.8'
    compile group: 'org.apache.logging.log4j', name: 'log4j-web', version: '2.8'
    compile group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '2.8'
}


gretty {
    servletContainer = 'tomcat8'
    httpPort = 8081
}

I tried these options:

  1. Change the log manager in a class that extends Application.

    package com.ahoxha.test;
    
    import java.util.HashSet;
    import java.util.Set;
    import javax.annotation.PostConstruct;
    import javax.ws.rs.ApplicationPath;
    import javax.ws.rs.core.Application;
    import com.ahoxha.filters.JerseyLoggingFilter;
    
    @ApplicationPath("")
    public class HelloApplication extends Application {
        @Override
        public final Set<Class<?>> getClasses() {
            final Set<Class<?>> classes = new HashSet<>();
            classes.add(Hello.class);
            classes.add(MessageResource.class);
            classes.add(JerseyLoggingFilter.class);
            return classes;
        }
    
        @PostConstruct
        public void init() {
            String cn = "org.apache.logging.log4j.jul.LogManager";
            System.setProperty("java.util.logging.manager", cn);
        }
    }
    
  2. Change the log manager in a class that implements ServletContextListener

    package com.ahoxha.context;
    
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;
    
    @WebListener
    public class ContextInitializer implements ServletContextListener {
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            System.out.println("Initializing context.");
            System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            System.out.println("Context destroyed.");
        }
    }
    
  3. Apart from two options above, I also tried to set the log manager in a static block (for both options 1. and 2.)

Unfortunately, none of these options worked for me. I am wondering where should this be done. Is there something that I am missing?

回答1:

java.util.logging.LogManager class gets initialized when web application container like tomcat, gretty gets started. At the time of loading (in static block), this class checks for the value of java.util.logging.manager system property and create the Logger accordingly. Once initialized, this class never gets initialized again.

So, for a web application, setting this system property through web application code would be too late.

One possible solution is to pass this system property value through VM arguments to the application container -

-Djava.util.logging.manager="org.apache.logging.log4j.jul.LogManager"

In this situation, you have to provide log4j jars and configuration file at the time of starting container so that org.apache.logging.log4j.ju‌​l.LogManager can be loaded through System ClassLoader.

For tomcat, following 3 jars you must load along with bootstrap.jar (tomcat startup), tomcat-juli.jar (logging) to make it work -

log4j-jul
log4j-api
log4j-core

Similar approach needs to be used for other container also.



回答2:

If you are not able or allowed to change the configuration of the container, here's an alternative solution that worked for me.

When the context is initialised, remove all the handlers that a JUL logger has, then add your own custom handler where you can use your preferred logging framework.

Here's an example:

import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;

@WebListener
public class ContextInitializer implements ServletContextListener {

    Logger logger = Logger.getLogger("");
    static final org.apache.logging.log4j.Logger log4jLogger = LogManager.getLogger();

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        for (Handler hanlder : logger.getHandlers()) {
            logger.removeHandler(hanlder);
        }
        logger.addHandler(new CustomHandler());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Context destroyed.");
    }

    private static class CustomHandler extends Handler {

        @Override
        public void publish(LogRecord record) {
            log4jLogger.log(CustomHandler.julToLog4jLevel(record.getLevel()), record.getMessage(), record.getThrown());
        }

        /**
         * Converts the logging level according to [default level conversions](https://logging.apache.org/log4j/2.0/log4j-jul/index.html)
         */
        private static Level julToLog4jLevel(java.util.logging.Level level) {
            if (level == java.util.logging.Level.WARNING) {
                return Level.WARN;
            } else if (level == java.util.logging.Level.SEVERE) {
                return Level.ERROR;
            } else if (level == java.util.logging.Level.INFO) {
                return Level.INFO;
            } else if (level == java.util.logging.Level.FINE) {
                return Level.DEBUG;
            } else if (level == java.util.logging.Level.FINER) {
                return Level.TRACE;
            } else if (level == java.util.logging.Level.FINEST) {
                return Level.ALL;
            }

            return null;
        }

        @Override
        public void flush() {

        }

        @Override
        public void close() throws SecurityException {

        }
    }
}


回答3:

I believe that "java.util.logging.LogManager" class initialisation happens before the moment when system properties are updated.

So, you could make logging libraries available to the container classloader, and configure this system property for the whole container using environment variables or configuration files.