Piping to Log4j using SLF4J - Not Working

I’m following the “Piping to Log4j using SLF4J” directions from the Vaadin website (https://vaadin.com/docs/v8/framework/advanced/advanced-logging.html#advanced.logging.slf4j), and have scoured the web, but haven’t had any luck getting this to work. Interestingly, after adding the VaadinServlet, the Spring annotations within classes annotated with @Route are no longer triggered, e.g., @Autowired and @PostConstruct. With regards to Spring-Boot, how can I get my servlet to behave like the default servlet that is auto-registered when not implementing my own?

Another factor that might be coming into play is that when trying to override the logging’s automatic loading and force set the configuration, I get the following output to stderr, but no exception is thrown “com.google.common.flogger.backend.log4j2.Log4j2BackendFactory.create SLF4JLogger cannot be cast to org.apache.logging.log4j.core.Logger” and it makes me wonder if Java’s/Vaadin’s logging is not propagating these messages back up the stack.

I started off trying to implement Google’s Flogger API with Log4J2 with Vaadin 16. This problem occurred and then just tried implementing Log4J2 with SFL4J Binding as suggested in the documentation. Same problem. Can anyone point me to a FULL sample project or codebase that accomplishes this?

Thanks in advance!

VaadinServlet:

@WebServlet(value = "/*", asyncSupported = true)
public class AppServlet extends VaadinServlet implements SessionInitListener {
  private static final long serialVersionUID = 3469585746542555263L;

  static {
    SLF4JBridgeHandler.removeHandlersForRootLogger();
    SLF4JBridgeHandler.install();
    java.util.logging.Logger.getGlobal().setLevel(Level.FINEST);
  }

  @Override
  protected void servletInitialized() throws ServletException {
    super.servletInitialized();
    getService().addSessionInitListener(this);
  }

  @Override
  public void sessionInit(SessionInitEvent event) throws ServiceException {
    VaadinSession session = event.getSession();
    NotificationErrorHandler errHandler = new NotificationErrorHandler();
    session.setErrorHandler(errHandler);
  }
}

Spring-Boot the VaadinServlet:

@Configuration
public class AppConfiguration {

  @Bean
  public ServletRegistrationBean<AppServlet> flexmonsterServlet() {
    ServletRegistrationBean<AppServlet> servRegBean = new ServletRegistrationBean<>();
    servRegBean.setServlet(new AppServlet());
    servRegBean.addUrlMappings("/*");
    servRegBean.setName("appServlet");
    servRegBean.setLoadOnStartup(1);
    return servRegBean;
  }
}

Logging Service:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class LoggingService {
  private static final Logger logger = LoggerFactory.getLogger(LoggingService.class);
  
  public void log(String msg, Level level) {

    switch (level.getName()) {
      case "INFO":
        logger.info(msg);
        break;
      case "WARNING":
        logger.warn(msg);
        break;
      case "SEVERE":
        logger.error(msg);
        break;
      case "FINE":
        logger.debug(msg);
        break;
      case "FINER":
        logger.debug(msg);
        break;
      case "FINEST":
        logger.debug(msg);
        break;
      case "CONFIG":
        logger.debug(msg);
        break;
    }
  }
}

src/main/resources/log4j2.properties

status=error
name=PropertiesConfig

property.filename=./target/logs
 
filter.threshold.type=ThresholdFilter
filter.threshold.level=debug
 
 # ConsoleAppender will print logs on console
appender.console.type=Console
appender.console.name=consoleLogger
appender.console.layout.type=PatternLayout
appender.console.layout.pattern=%m%n
appender.console.filter.threshold.type=ThresholdFilter
appender.console.filter.threshold.level=error
 
 # RollingFileAppender will print logs in file which can be rotated based on time or size
appender.rolling.type=RollingFile
appender.rolling.name=fileLogger
appender.rolling.fileName=${filename}/app.log
appender.rolling.filePattern=${filename}/app-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz
appender.rolling.layout.type=PatternLayout
appender.rolling.layout.pattern=%d %p %C{1.} [%t]
 %m%n
appender.rolling.policies.type=Policies
appender.rolling.policies.time.type=TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval=2
appender.rolling.policies.time.modulate=true
appender.rolling.policies.size.type=SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=100MB
appender.rolling.strategy.type=DefaultRolloverStrategy
appender.rolling.strategy.max=5
 
 # Mention package name here. Classes in this package or subpackages will use ConsoleAppender and RollingFileAppender for logging         
logger.rolling.name=com.example.app
logger.rolling.level=debug
logger.rolling.additivity=false
logger.rolling.appenderRef.rolling.ref=fileLogger
logger.rolling.appenderRef.rolling.ref=consoleLogger

# Configure root logger for logging error logs in classes which are in package other than above specified package
rootLogger.level=info
rootLogger.additivity=false
rootLogger.appenderRef.rolling.ref=fileLogger
rootLogger.appenderRef.console.ref=consoleLogger

pom.xml:

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
    <scope>test</scope>
</dependency>
		
<!-- https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.30</version>
</dependency>

Psuedo-code showing attempt to load file:

LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false);
File file = new File("path/to/a/different/log4j2.properties");

// this will force a reconfiguration
context.setConfigLocation(file.toURI());

With regards to Spring-Boot, how can I get my servlet to behave like the default servlet that is auto-registered when not implementing my own?

You need to extend VaadinSpringServlet (or SpringVaadinServlet? Can’t remember) instead - it contains Spring integration bits.

Regarding logging: so you’re piping JUL to slf4j, which is then using log4j12 as its logging backend. The exception “com.google.common.flogger.backend.log4j2.Log4j2BackendFactory.create SLF4JLogger cannot be cast to org.apache.logging.log4j.core.Logger” sounds like coming from the create() method of the Log4j2BackendFactory class - please take a look into the implementation of that class, to see what is going on. Perhaps the stack trace is only logged on the DEBUG level or the like.

I may be wrong, but if it seems tome that Log4j 2 SLF4J Binding is log4j-slf4j-impl (or log4j-slf4j18-impl if using SLF4J 1.8.x).

The dependency suggested in Vaadin docs (slf4j-log4j12) relates to Log4J, not Log4j2

HTH

Marco

Martin Vyšný:

With regards to Spring-Boot, how can I get my servlet to behave like the default servlet that is auto-registered when not implementing my own?

You need to extend VaadinSpringServlet (or SpringVaadinServlet? Can’t remember) instead - it contains Spring integration bits.

Thanks for the suggestions! Unfortunately I get “SpringVaadinServlet cannot be resolved to a type”. I think I have the right dependencies in the POM, but no luck. Is SpringVaadinServlet only available in earlier versions?

<dependency>
	<groupId>com.vaadin</groupId>
	<artifactId>vaadin-spring-boot-starter</artifactId>
	<exclusions>
		<!-- Excluding so that webjars are not included. -->
		<exclusion>
			<groupId>com.vaadin</groupId>
			<artifactId>vaadin-core</artifactId>
		</exclusion>
	</exclusions>
</dependency>

<dependency>
  <groupId>com.vaadin</groupId>
  <artifactId>vaadin-spring</artifactId>
  <version>13.0.2</version>
</dependency>

Regarding logging: so you’re piping JUL to slf4j, which is then using log4j12 as its logging backend. The exception “com.google.common.flogger.backend.log4j2.Log4j2BackendFactory.create SLF4JLogger cannot be cast to org.apache.logging.log4j.core.Logger” sounds like coming from the create() method of the Log4j2BackendFactory class - please take a look into the implementation of that class, to see what is going on. Perhaps the stack trace is only logged on the DEBUG level or the like.

What should I be looking at exactly? This is what I’m using to try and force loading the properties file:

  static {
    try {
      ClassPathResource config = new ClassPathResource("log4j2.properties");
      ConfigurationSource source = new ConfigurationSource(config.getInputStream());
      Configurator.initialize(null, source); // ERROR StatusLogger LogManager returned an instance of org.apache.logging.slf4j.SLF4JLoggerContextFactory which does not implement org.apache.logging.log4j.core.impl.Log4jContextFactory. Unable to initialize Log4j.

    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }

Marco Collovati:
I may be wrong, but if it seems tome that Log4j 2 SLF4J Binding is log4j-slf4j-impl (or log4j-slf4j18-impl if using SLF4J 1.8.x).

The dependency suggested in Vaadin docs (slf4j-log4j12) relates to Log4J, not Log4j2

HTH

Marco

Thanks Marco for the suggestions. With an adapter to bridge SLF4J and Log4J2, it should be theoretically possible to route these messages. So I removed the other dependencies aforementioned and tried the following as you suggested since I am using slf4j-api: 1.7.30.

<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.13.3</version>
    <scope>test</scope>
</dependency>

But I had no luck. With regards to Log4J, I’m not really interested in using a legacy API that is no longer maintained which is why I am trying to use Log4J2.

I’m also having the same problem when trying to accomplish this with log4j.

Had to exclude logback and use log4j2 as the implementation instead. The following changes to my pom.xml got this working. If you’re reading this, also note that you will want to restart the application entirely if you are making these changes while debugging so Spring Boot will reload the logger.

		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter</artifactId>
		    <exclusions>
		        <exclusion>
		            <groupId>org.springframework.boot</groupId>
		            <artifactId>spring-boot-starter-logging</artifactId>
		        </exclusion>
		    </exclusions>
		</dependency>
		<!-- Add Log4j2 Dependency -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		
		<dependency>
		  <groupId>org.apache.logging.log4j</groupId>
		  <artifactId>log4j-slf4j18-impl</artifactId>
		  <version>2.13.3</version>
		</dependency>
		<dependency>
		  <groupId>org.apache.logging.log4j</groupId>
		  <artifactId>log4j-api</artifactId>
		  <version>2.13.3</version>
		</dependency>
		<dependency>
		  <groupId>org.apache.logging.log4j</groupId>
		  <artifactId>log4j-core</artifactId>
		  <version>2.13.3</version>
		</dependency>