简体   繁体   中英

Log4j Commons Logging Bridge ignored

Our web app runs in a tomcat 9 container and uses Log4j 2.13.3 as logging system.

The web app includes org.apache.xmlgraphics:fop 2.3, which uses apache commons-logging (version 1.2 in our setup instead of 1.0.4 originally used in fop).

This combination has been running for years using log4-jcl, the Commons Logging Bridge, and all commons-logging output correctly went into the files configured by log4j. The configuration is fairly easy, as described in this SO answer

However, for no apparent reason, fop has recently started writing all its (rather verbose) logs to stderr (ie directly to catalina.out) instead of the log file configured, but only on some of our systems. We have some 50 tomcat instances running basically identical software and identical system configurations; one third of them still logs correctly.

I assume that commons-logging doesn't find log4j-jcl's implementation of org.apache.commons.logging.LogFactory.

Debugging log4j using Configuration status="trace" doesn't provide any hint of a failure in log4j-jcl or elsewhere.

I'm out of my wits.Any suggestions?

Answering my own question in case it's helpful to others.

I needed to add a file called commons-logging.properties in the classpath, containing one line:

org.apache.commons.logging.LogFactory=org.apache.logging.log4j.jcl.LogFactoryImpl

Kudos to Piotr for his comment about setting org.apache.commons.logging.diagnostics.dest.

Apparently, if the class is not explicitly specified, the implementation of LogFactory that is actually used depends on the (somewhat random) order the classloader loads the potential candidates.

I my webapp a maven dependency brought sl4f along as a transitive dependency, so on some systems slf4j was chosen instead of log4j, but slf4 didn't have a valid configuration and defaulted to STDERR.

While your answer explains why Log4j 2.x is not chosen as JCL's backend, some details don't add up: the original commons-logging never chooses SLF4j as backend (cf. source code ). Hence the situation must be more complicated:

  • The original commons-logging discovers alternative LogFactory implementations through the ServiceLoader utility.
  • You must have two alternative LogFactory implementations in your classpath: log4j-jcl and spring-jcl . The latter is a complete replacement for the standard commons-logging library (ie it contains a copy of org.apache.commons.logging.LogFactory itself) and has some complex backend selection rules (cf. source code ), which in your case prefer SLF4J over Log4j 2.x.

Since the classloader randomly loads the org.apache.commons.logging.LogFactory class from either commons-logging or spring-jcl you can have two kinds of problems:

  1. if the commons-logging version is chosen the ServiceLoader randomly loads log4j-jcl 's implementation or spring-jcl 's implementation. You already solved this case in your answer.
  2. if the spring-jcl 's version is chosen the SLF4J backend will be used.

To solve all these potential problems I would:

  1. Remove the original commons-logging and keep the clone spring-jcl . Even better I would exclude both and use the deterministic jcl-over-slf4j (complete JCL replacement that always uses SLF4J)
  2. Remove log4j-jcl since it is no longer needed (it is only required by the original commons-logging ),
  3. Add the log4j-slf4j-impl SLF4J binding.

This solution adds an additional API to your logging configuration (JCL -> SLF4J -> Log4j 2.x), but guarantees stability. It is also the way spring-boot-starter-log4j2 works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM