How to run jul-to-slf4j bridge once per JVM?

432 Views Asked by At

I'd like to run Surefire in parallel mode (multiple JVMs) where each JVM must run:

SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();

exactly once before the first test. How can this be done?

1

There are 1 best solutions below

1
mfulton26 On BEST ANSWER

There are various ways to make some code run at the beginning of a test suite.

Here are 4 (I'm sure there are more):

  1. JUnit via RunWith Suite with Suite.SuiteClasses and BeforeClass (adapted from examples in SuiteTest):

    @RunWith(Suite.class)
    @SuiteClasses({FirstTest.class, SecondTest.class/*, ...*/, LastTest.class})
    public static class AllWithSLF4JBridgeHandler {
        @BeforeClass
        public static void registerRootLoggerHandlers() {
            SLF4JBridgeHandler.removeHandlersForRootLogger();
            SLF4JBridgeHandler.install();
        }
    }
    
  2. TestNG with BeforeSuite:

    /**
     * Base class for each test class (i.e. every test class should extend this class).
     */
    public abstract class BaseTest {
        @BeforeSuite
        public void registerRootLoggerHandlers() {
            SLF4JBridgeHandler.removeHandlersForRootLogger();
            SLF4JBridgeHandler.install();
        }
    }
    
  3. TestNG with Guice:

    /**
     * Test module. Each test class should be annotated with `@Guice(TestModule.class)`.
     */
    public class TestModule implements Module {
        @Override
        public void configure(Binder binder) {
            SLF4JBridgeHandler.removeHandlersForRootLogger();
            SLF4JBridgeHandler.install();
        }
    }
    
  4. Static initialization blocks (test-framework independent):

    /**
     * Base class for each test class (i.e. every test class should extend this class).
     */
    public abstract class BaseTest {
        static {
            SLF4JBridgeHandler.removeHandlersForRootLogger();
            SLF4JBridgeHandler.install();
        }
    }
    

I'm not sure how all of these methods work with Surefire's parallel mode. Methods 1 and 2 may not work there but I believe methods 3 and 4 should.


Another option would be to not use the programmatic installation of the SLF4JBridgeHandler but to use a java.util.logging.config file or class (see LogManager):

  1. "java.util.logging.config.file":

    logging.properties file:

    // register SLF4JBridgeHandler as handler for the j.u.l. root logger
    handlers = org.slf4j.bridge.SLF4JBridgeHandler
    

    System property assignment:

    java -Djava.util.logging.config.file=/path/to/logging.properties ...
    

    This works well if you know the path to your logging file beforehand.

  2. "java.util.logging.config.class":

    Using a file may not be a good option if you're deploying a WAR and don't know where the file will be, etc. so alternatively you can create a logging config class:

    public class SLF4JBridgeHandlerInitializer {
        public SLF4JBridgeHandlerInitializer() throws IOException {
            String loggingConfigurationString = "handlers = " + SLF4JBridgeHandler.class.getName();
            InputStream inputStream = new ByteArrayInputStream(loggingConfigurationString.getBytes());
            LogManager.getLogManager().readConfiguration(inputStream);
        }
    }
    

    System property assignment:

    java -Djava.util.logging.config.class=package.SLF4JBridgeHandlerInitializer ...
    

    I've done this before and it has worked well for me (SLF4JBridgeHandler.Initializer by mfulton26 · Pull Request #57 · qos-ch/slf4j).

These final two options should initialize each JVM instance as long as the appropriate system property is set.