How to make Jersey to use SLF4J instead of JUL?

2019-02-02 10:23发布

问题:

I've found a useful article that explains how to make Jersey to use SLF4J instead of JUL. Now my unit test looks like (and it works perfectly):

public class FooTest extends JerseyTest {
  @BeforeClass
  public static void initLogger() {
    java.util.logging.Logger rootLogger =
      java.util.logging.LogManager.getLogManager().getLogger("");
    java.util.logging.Handler[] handlers = rootLogger.getHandlers();
    for (int i = 0; i < handlers.length; i++) {
      rootLogger.removeHandler(handlers[i]);
    }
    org.slf4j.bridge.SLF4JBridgeHandler.install();
  }
  public FooTest() {
    super("com.XXX");
  }
  @Test
  public void testSomething() throws Exception {
    // ...
  }
}

My pom.xml includes these dependencies:

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jul-to-slf4j</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.16</version>
</dependency>

It works perfectly, but I don't want to make the same configuration in every unit test. It's an obvious code duplication, which I would like to avoid. How can I do this more effectively?

ps. Maybe it's not possible to optimize the code above and I'm doing the best I can?

回答1:

If you are using the client API you can manually redirect the logs to slf4j (note that it may break in future versions although it seems unlikely):

Logger LOG = LoggerFactory.getLogger(MyClass.class); //slf4j logger

WebTarget ws = ClientBuilder.newClient(config)
                  .register(new LoggingFilter(new JulFacade(), true));

private static class JulFacade extends java.util.logging.Logger {
  JulFacade() { super("Jersey", null); }
  @Override public void info(String msg) { LOG.info(msg); }
}


回答2:

The best way to do it is through a custom Listener. Being initialized before JSF servlet it should configure jul-to-slf4j bridge in contextInitialized(ServletContextEvent).



回答3:

What it sounds like is you'd want the JUL/SLF4J configuration handle before JUnit starts testing so it could be covered for all tests? Here's a way you could do that.

Output

MySuite.init()
MySuite()
getSuiteTests()
MyTest.init()
MyTest()
test()

Code

@RunWith(AbstractTestSuite.TestSuiteRunner.class)
public abstract class AbstractTestSuite {
   public static class TestSuiteRunner extends Suite {
      public TestSuiteRunner(Class<?> klass) throws Exception {
         super(klass, ((Class<? extends AbstractTestSuite>) klass).newInstance().getSuiteClasses());
      }
   }

   public Class<?>[] getSuiteClasses() {
      List<Class<?>> all = new ArrayList<Class<?>>();
      for (Class<?> testClass : getSuiteTests()) {
         all.add(testClass);
      }
      return all.toArray(new Class<?>[0]);
   }

   protected abstract Iterable<Class<?>> getSuiteTests();
}

public class MySuite extends AbstractTestSuite {
   public static class MyTest {
      static {
         System.out.println("MyTest.init()");
      }

      public MyTest() {
         System.out.println("MyTest()");
      }

      @Test
      public void test() {
         System.out.println("test()");
         assertTrue(true);
      }
   }

   static {
      System.out.println("MySuite.init()");
   }

   public MySuite() {
      System.out.println("MySuite()");
   }

   @Override
   protected Iterable<Class<?>> getSuiteTests() {
      System.out.println("getSuiteTests()");
      return Arrays.asList(new Class<?>[] {MyTest.class});
   }
}


回答4:

In my app Jersey logging (with proper pom.xml and logback.xml) works fine only with

    SLF4JBridgeHandler.install(); 

For tests you can make base abstract test with common configuration and extends other JUnit from it:

public abstract class AbstractTest {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractTest.class);

    static {
        SLF4JBridgeHandler.install();
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Rule
    // http://stackoverflow.com/questions/14892125/what-is-the-best-practice-to-determine-the-execution-time-of-the-bussiness-relev
    public Stopwatch stopwatch = new Stopwatch() {
        @Override
        protected void finished(long nanos, Description description) {
           ...


回答5:

This worked for me:

public abstract class JerseyTestSFL4J extends JerseyTest {

  static {
    // Get JerseyTest to use SLF4J instead of JUL
    SLF4JBridgeHandler.removeHandlersForRootLogger();
    SLF4JBridgeHandler.install();
  }
}

and then having my tests extend JerseyTestSFL4J.



回答6:

Slf4jLogger from org.apache.cxf:cxf-core package is another option. It implements java.util.logging.Logger and delegate cals to slf4J.

Jersey server:

ResourceConfig config = new ResourceConfig(HelloWorldResource.class);
config.register(
    new Slf4jLogger(this.getClass().getName(), null));

Jersey client:

ClientBuilder
    .newClient()
    .register(
        new LoggingFeature(
                new Slf4jLogger(this.getClass().getName(), null)));