log-captor: org.apache.logging.log4j loggers not supported like test shows

I’m working with apache.logging.log4j on existing classes, and followed the example test as below.

//Basic example class
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class FooService {

    private static final Logger LOGGER = LogManager.getLogger(FooService.class);

    public void sayHello() {
        LOGGER.info("Keyboard not responding. Press any key to continue...");
        LOGGER.warn("Congratulations, you are pregnant!");
    }

}

//Unit test:
import nl.altindag.log.LogCaptor;

public class FooServiceShould {
    @Test
    public void logInfoAndWarnMessages() {
        String expectedInfoMessage = "Keyboard not responding. Press any key to continue...";
        String expectedWarnMessage = "Congratulations, you are pregnant!";

        LogCaptor logCaptor = LogCaptor.forClass(FooService.class);
        //...
    }
}

However, i get the error shown below log4j-slf4j-impl cannot be present with log4j-to-slf4j using the same libraries as the demo.

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; //all existing classes are already setup with these libs

public class Address {
    
    private static final Logger logger = LogManager.getLogger(Address.class);
    
    public Address(int a, int b, int c, int d) {
    	//other code
        logger.error("Address is malformed.");
    }
}

import nl.altindag.log.LogCaptor;

public class AddressTest {
    @Test
    public void testAddressValidation() {
        LogCaptor logCaptor = LogCaptor.forClass(Address.class);
        
        new Address(999,0,0,0);
        
        assertTrue(logCaptor.getErrorLogs().contains("Address is malformed."));
    }
}
org.apache.logging.log4j.LoggingException: log4j-slf4j-impl cannot be present with log4j-to-slf4j
	at org.apache.logging.slf4j.Log4jLoggerFactory.validateContext(Log4jLoggerFactory.java:49)
	at org.apache.logging.slf4j.Log4jLoggerFactory.newLogger(Log4jLoggerFactory.java:39)
	at org.apache.logging.slf4j.Log4jLoggerFactory.newLogger(Log4jLoggerFactory.java:30)
	at org.apache.logging.log4j.spi.AbstractLoggerAdapter.getLogger(AbstractLoggerAdapter.java:54)
	at org.apache.logging.slf4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:30)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)
	at nl.altindag.log.LogCaptor.<init>(LogCaptor.java:49)
	at nl.altindag.log.LogCaptor.forClass(LogCaptor.java:69)
	at ....AddressTest.testAddressValidation(AddressTest.java:20)

and the console output

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:~/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.13.3/log4j-slf4j-impl-2.13.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:~/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]

I’m not sure if i’m applying the pom exclusions properly, but it seems like it might be easier to make a version that is compatible with org.apache.logging.log4j instead

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 15 (7 by maintainers)

Most upvoted comments

Aha #2, removing the exclusion we tried adding for one of the above problems finally fixed it. So in summary, import the library before other log4j dependencies.

image

Amazing, you found the solution. Interesting to see that the order of declaring the dependency in this specific use-case worked for you! I will note this solution down. Good luck asserting your log messages!

By the way I published here Java tutorials - LogCaptor isolated examples with different logging frameworks include this example and the adjustments to the pom which are included to get it working.

Update 14-07-2021 I noticed that you are using spring-boot-starter-log4j2. This library has some transitive dependencies which indeed requires adjustments to your pom. I have added an example for this use case, see the example project here: https://github.com/Hakky54/java-tutorials/tree/main/log-captor-examples/log-captor-with-spring-boot-starter-log4j2

So you basically need to add the following configuration to your pom:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <classpathDependencyExcludes>
                    <classpathDependencyExclude>org.apache.logging.log4j:log4j-slf4j-impl</classpathDependencyExclude>
                </classpathDependencyExcludes>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <configuration>
                <classpathDependencyExcludes>
                    <classpathDependencyExclude>org.apache.logging.log4j:log4j-slf4j-impl</classpathDependencyExclude>
                </classpathDependencyExcludes>
            </configuration>
        </plugin>
    </plugins>
</build>

This will exclude org.apache.logging.log4j:log4j-slf4j-impl from the classpath only during the test phase I mentioned this solution also at the beginning of our conversation, see here: https://github.com/Hakky54/log-captor/issues/6#issuecomment-770237736

Hi Derek,

Thank you for this detailed issue report! This is a typical SLF4J error message that is preventing to have more than one slf4j binding to be present at the classpath.

The log4j-to-slf4j dependency within this library makes it possible to capture logs from apache when the end user is using apache log4j-core while not using slf4j api/slf4j bridge/slf4j binder at all. This makes it convenient for those end-users to start without the need for additional configuration. Even if this library didn’t included log4j-to-slf4j you would still have a similar error. You can try it by excluding the specific library with the following snippet:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>logcaptor</artifactId>
    <version>2.3.2</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>

The main reason why this is happening is that your classpath already has a slf4j implementation and LogCaptor too. This is also mentioned here LogCaptor - Using Log Captor alongside with other logging libraries. So what you need to do is just exclude your slf4j implementation during the test phase. If you are using maven surefire or failsafe plugin you can add the following snippet to your pom:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <classpathDependencyExcludes>
                    <classpathDependencyExclude>org.apache.logging.log4j:log4j-slf4j-impl</classpathDependencyExclude>
                </classpathDependencyExcludes>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <configuration>
                <classpathDependencyExcludes>
                    <classpathDependencyExclude>org.apache.logging.log4j:log4j-slf4j-impl</classpathDependencyExclude>
                </classpathDependencyExcludes>
            </configuration>
        </plugin>
    </plugins>
</build>

This configuration is excluding your log4j-slf4j-impl during the test-phase only. Can you give it a try and share your results here? I think excluding log4j-to-slf4j from LogCaptor would not be needed.

I’m not sure if i’m applying the pom exclusions properly, but it seems like it might be easier to make a version that is compatible with org.apache.logging.log4j instead

I need to think about it. Thank you for the suggestion!