Browse Source

Suppress deprecation warning on JDK 9 (for LogRecord.setMillis)

Includes revised Log methods in log level order, with consistent delegation of fatal->error for log level checks in SLF4J and JUL, a JavaUtilDelegate for defensive access to java.logging on JDK 9, support for LogRecord message objects, as well as revised log setup recommendations.

Issue: SPR-15453
Issue: SPR-14512
pull/1392/head
Juergen Hoeller 9 years ago
parent
commit
d50dab6ea9
  1. 276
      spring-core/src/main/java/org/apache/commons/logging/LogFactory.java
  2. 292
      src/docs/asciidoc/overview.adoc

276
spring-core/src/main/java/org/apache/commons/logging/LogFactory.java

@ -17,6 +17,7 @@
package org.apache.commons.logging; package org.apache.commons.logging;
import java.io.Serializable; import java.io.Serializable;
import java.util.logging.LogRecord;
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -28,13 +29,14 @@ import org.slf4j.spi.LocationAwareLogger;
/** /**
* A minimal incarnation of Apache Commons Logging's {@code LogFactory} API, * A minimal incarnation of Apache Commons Logging's {@code LogFactory} API,
* providing just the two common static {@link Log} lookup methods. * providing just the common {@link Log} lookup methods. This is inspired
* This should be source and binary compatible with all common use of the * by the JCL-over-SLF4J bridge and should be source as well as binary
* Commons Logging API (i.e. {@code LogFactory.getLog(Class/String)} setup). * compatible with all common use of the Commons Logging API (in particular:
* with {@code LogFactory.getLog(Class/String)} field initializers).
* *
* <p>This implementation does not support any of Commons Logging's flexible * <p>This implementation does not support Commons Logging's original provider
* configuration. It rather only checks for the presence of the Log4J 2.x API * detection. It rather only checks for the presence of the Log4j 2.x API
* and the SLF4J 1.7 API in the framework classpath, falling back to * and the SLF4J 1.7 API in the Spring Framework classpath, falling back to
* {@code java.util.logging} if none of the two is available. In that sense, * {@code java.util.logging} if none of the two is available. In that sense,
* it works as a replacement for the Log4j 2 Commons Logging bridge as well as * it works as a replacement for the Log4j 2 Commons Logging bridge as well as
* the JCL-over-SLF4J bridge, both of which become irrelevant for Spring-based * the JCL-over-SLF4J bridge, both of which become irrelevant for Spring-based
@ -42,11 +44,18 @@ import org.slf4j.spi.LocationAwareLogger;
* Commons Logging API jar anymore either). Furthermore, for simple setups * Commons Logging API jar anymore either). Furthermore, for simple setups
* without an external logging provider, Spring does not require any extra jar * without an external logging provider, Spring does not require any extra jar
* on the classpath anymore since this embedded log factory automatically * on the classpath anymore since this embedded log factory automatically
* switches to {@code java.util.logging} in such a scenario. * delegates to {@code java.util.logging} in such a scenario.
* *
* <p><b>Note that this Commons Logging variant is only meant to be used for * <p><b>Note that this Commons Logging variant is only meant to be used for
* framework logging purposes, both in the core framework and in extensions.</b> * infrastructure logging purposes in the core framework and in extensions.</b>
* For applications, prefer direct use of Log4J or SLF4J or {@code java.util.logging}. * It also serves as a common bridge for third-party libraries using the
* Commons Logging API, e.g. Apache HttpClient, Castor and HtmlUnit, bringing
* them into the same consistent arrangement without any extra bridge jars.
*
* <p><b>For logging need in application code, prefer direct use of Log4j 2.x
* or SLF4J or {@code java.util.logging}.</b> Simply put Log4j 2.x or Logback
* (or another SLF4J provider) onto your classpath, without any extra bridges,
* and let the framework auto-adapt to your choice.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 5.0 * @since 5.0
@ -58,11 +67,11 @@ public abstract class LogFactory {
static { static {
ClassLoader cl = LogFactory.class.getClassLoader(); ClassLoader cl = LogFactory.class.getClassLoader();
try { try {
// Try Log4J 2.x API // Try Log4j 2.x API
cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger"); cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");
logApi = LogApi.LOG4J; logApi = LogApi.LOG4J;
} }
catch (ClassNotFoundException ex) { catch (ClassNotFoundException ex1) {
try { try {
// Try SLF4J 1.7 SPI // Try SLF4J 1.7 SPI
cl.loadClass("org.slf4j.spi.LocationAwareLogger"); cl.loadClass("org.slf4j.spi.LocationAwareLogger");
@ -103,7 +112,13 @@ public abstract class LogFactory {
case SLF4J: case SLF4J:
return Slf4jDelegate.createLog(name); return Slf4jDelegate.createLog(name);
default: default:
return new JavaUtilLog(name); // Defensively use lazy-initializing delegate class here as well since the
// java.logging module is not present by default on JDK 9. We are requiring
// its presence if neither Log4j nor SLF4J is available; however, in the
// case of Log4j or SLF4J, we are trying to prevent early initialization
// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
// trying to parse the bytecode for all the cases of this switch clause.
return JavaUtilDelegate.createLog(name);
} }
} }
@ -167,6 +182,14 @@ public abstract class LogFactory {
} }
private static class JavaUtilDelegate {
public static Log createLog(String name) {
return new JavaUtilLog(name);
}
}
@SuppressWarnings("serial") @SuppressWarnings("serial")
private static class Log4jLog implements Log, Serializable { private static class Log4jLog implements Log, Serializable {
@ -182,8 +205,8 @@ public abstract class LogFactory {
} }
@Override @Override
public boolean isDebugEnabled() { public boolean isFatalEnabled() {
return logger.isEnabled(Level.DEBUG, null, null); return logger.isEnabled(Level.FATAL, null, null);
} }
@Override @Override
@ -192,8 +215,8 @@ public abstract class LogFactory {
} }
@Override @Override
public boolean isFatalEnabled() { public boolean isWarnEnabled() {
return logger.isEnabled(Level.FATAL, null, null); return logger.isEnabled(Level.WARN, null, null);
} }
@Override @Override
@ -202,23 +225,23 @@ public abstract class LogFactory {
} }
@Override @Override
public boolean isTraceEnabled() { public boolean isDebugEnabled() {
return logger.isEnabled(Level.TRACE, null, null); return logger.isEnabled(Level.DEBUG, null, null);
} }
@Override @Override
public boolean isWarnEnabled() { public boolean isTraceEnabled() {
return logger.isEnabled(Level.WARN, null, null); return logger.isEnabled(Level.TRACE, null, null);
} }
@Override @Override
public void debug(Object message) { public void fatal(Object message) {
logger.logIfEnabled(FQCN, Level.DEBUG, null, message, null); logger.logIfEnabled(FQCN, Level.FATAL, null, message, null);
} }
@Override @Override
public void debug(Object message, Throwable exception) { public void fatal(Object message, Throwable exception) {
logger.logIfEnabled(FQCN, Level.DEBUG, null, message, exception); logger.logIfEnabled(FQCN, Level.FATAL, null, message, exception);
} }
@Override @Override
@ -232,13 +255,13 @@ public abstract class LogFactory {
} }
@Override @Override
public void fatal(Object message) { public void warn(Object message) {
logger.logIfEnabled(FQCN, Level.FATAL, null, message, null); logger.logIfEnabled(FQCN, Level.WARN, null, message, null);
} }
@Override @Override
public void fatal(Object message, Throwable exception) { public void warn(Object message, Throwable exception) {
logger.logIfEnabled(FQCN, Level.FATAL, null, message, exception); logger.logIfEnabled(FQCN, Level.WARN, null, message, exception);
} }
@Override @Override
@ -252,23 +275,23 @@ public abstract class LogFactory {
} }
@Override @Override
public void trace(Object message) { public void debug(Object message) {
logger.logIfEnabled(FQCN, Level.TRACE, null, message, null); logger.logIfEnabled(FQCN, Level.DEBUG, null, message, null);
} }
@Override @Override
public void trace(Object message, Throwable exception) { public void debug(Object message, Throwable exception) {
logger.logIfEnabled(FQCN, Level.TRACE, null, message, exception); logger.logIfEnabled(FQCN, Level.DEBUG, null, message, exception);
} }
@Override @Override
public void warn(Object message) { public void trace(Object message) {
logger.logIfEnabled(FQCN, Level.WARN, null, message, null); logger.logIfEnabled(FQCN, Level.TRACE, null, message, null);
} }
@Override @Override
public void warn(Object message, Throwable exception) { public void trace(Object message, Throwable exception) {
logger.logIfEnabled(FQCN, Level.WARN, null, message, exception); logger.logIfEnabled(FQCN, Level.TRACE, null, message, exception);
} }
} }
@ -285,40 +308,36 @@ public abstract class LogFactory {
this.logger = logger; this.logger = logger;
} }
public boolean isDebugEnabled() { public boolean isFatalEnabled() {
return this.logger.isDebugEnabled(); return isErrorEnabled();
} }
public boolean isErrorEnabled() { public boolean isErrorEnabled() {
return this.logger.isErrorEnabled(); return this.logger.isErrorEnabled();
} }
public boolean isFatalEnabled() { public boolean isWarnEnabled() {
return this.logger.isErrorEnabled(); return this.logger.isWarnEnabled();
} }
public boolean isInfoEnabled() { public boolean isInfoEnabled() {
return this.logger.isInfoEnabled(); return this.logger.isInfoEnabled();
} }
public boolean isTraceEnabled() { public boolean isDebugEnabled() {
return this.logger.isTraceEnabled(); return this.logger.isDebugEnabled();
} }
public boolean isWarnEnabled() { public boolean isTraceEnabled() {
return this.logger.isWarnEnabled(); return this.logger.isTraceEnabled();
} }
public void debug(Object message) { public void fatal(Object message) {
if (message instanceof String || this.logger.isDebugEnabled()) { error(message);
this.logger.debug(String.valueOf(message));
}
} }
public void debug(Object message, Throwable exception) { public void fatal(Object message, Throwable exception) {
if (message instanceof String || this.logger.isDebugEnabled()) { error(message, exception);
this.logger.debug(String.valueOf(message), exception);
}
} }
public void error(Object message) { public void error(Object message) {
@ -333,12 +352,16 @@ public abstract class LogFactory {
} }
} }
public void fatal(Object message) { public void warn(Object message) {
error(message); if (message instanceof String || this.logger.isWarnEnabled()) {
this.logger.warn(String.valueOf(message));
}
} }
public void fatal(Object message, Throwable exception) { public void warn(Object message, Throwable exception) {
error(message, exception); if (message instanceof String || this.logger.isWarnEnabled()) {
this.logger.warn(String.valueOf(message), exception);
}
} }
public void info(Object message) { public void info(Object message) {
@ -353,27 +376,27 @@ public abstract class LogFactory {
} }
} }
public void trace(Object message) { public void debug(Object message) {
if (message instanceof String || this.logger.isTraceEnabled()) { if (message instanceof String || this.logger.isDebugEnabled()) {
this.logger.trace(String.valueOf(message)); this.logger.debug(String.valueOf(message));
} }
} }
public void trace(Object message, Throwable exception) { public void debug(Object message, Throwable exception) {
if (message instanceof String || this.logger.isTraceEnabled()) { if (message instanceof String || this.logger.isDebugEnabled()) {
this.logger.trace(String.valueOf(message), exception); this.logger.debug(String.valueOf(message), exception);
} }
} }
public void warn(Object message) { public void trace(Object message) {
if (message instanceof String || this.logger.isWarnEnabled()) { if (message instanceof String || this.logger.isTraceEnabled()) {
this.logger.warn(String.valueOf(message)); this.logger.trace(String.valueOf(message));
} }
} }
public void warn(Object message, Throwable exception) { public void trace(Object message, Throwable exception) {
if (message instanceof String || this.logger.isWarnEnabled()) { if (message instanceof String || this.logger.isTraceEnabled()) {
this.logger.warn(String.valueOf(message), exception); this.logger.trace(String.valueOf(message), exception);
} }
} }
@ -392,16 +415,12 @@ public abstract class LogFactory {
super(logger); super(logger);
} }
public void debug(Object message) { public void fatal(Object message) {
if (message instanceof String || this.logger.isDebugEnabled()) { error(message);
this.logger.log(null, FQCN, LocationAwareLogger.DEBUG_INT, String.valueOf(message), null, null);
}
} }
public void debug(Object message, Throwable exception) { public void fatal(Object message, Throwable exception) {
if (message instanceof String || this.logger.isDebugEnabled()) { error(message, exception);
this.logger.log(null, FQCN, LocationAwareLogger.DEBUG_INT, String.valueOf(message), null, exception);
}
} }
public void error(Object message) { public void error(Object message) {
@ -416,12 +435,16 @@ public abstract class LogFactory {
} }
} }
public void fatal(Object message) { public void warn(Object message) {
error(message); if (message instanceof String || this.logger.isWarnEnabled()) {
this.logger.log(null, FQCN, LocationAwareLogger.WARN_INT, String.valueOf(message), null, null);
}
} }
public void fatal(Object message, Throwable exception) { public void warn(Object message, Throwable exception) {
error(message, exception); if (message instanceof String || this.logger.isWarnEnabled()) {
this.logger.log(null, FQCN, LocationAwareLogger.WARN_INT, String.valueOf(message), null, exception);
}
} }
public void info(Object message) { public void info(Object message) {
@ -436,27 +459,27 @@ public abstract class LogFactory {
} }
} }
public void trace(Object message) { public void debug(Object message) {
if (message instanceof String || this.logger.isTraceEnabled()) { if (message instanceof String || this.logger.isDebugEnabled()) {
this.logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, String.valueOf(message), null, null); this.logger.log(null, FQCN, LocationAwareLogger.DEBUG_INT, String.valueOf(message), null, null);
} }
} }
public void trace(Object message, Throwable exception) { public void debug(Object message, Throwable exception) {
if (message instanceof String || this.logger.isTraceEnabled()) { if (message instanceof String || this.logger.isDebugEnabled()) {
this.logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, String.valueOf(message), null, exception); this.logger.log(null, FQCN, LocationAwareLogger.DEBUG_INT, String.valueOf(message), null, exception);
} }
} }
public void warn(Object message) { public void trace(Object message) {
if (message instanceof String || this.logger.isWarnEnabled()) { if (message instanceof String || this.logger.isTraceEnabled()) {
this.logger.log(null, FQCN, LocationAwareLogger.WARN_INT, String.valueOf(message), null, null); this.logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, String.valueOf(message), null, null);
} }
} }
public void warn(Object message, Throwable exception) { public void trace(Object message, Throwable exception) {
if (message instanceof String || this.logger.isWarnEnabled()) { if (message instanceof String || this.logger.isTraceEnabled()) {
this.logger.log(null, FQCN, LocationAwareLogger.WARN_INT, String.valueOf(message), null, exception); this.logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, String.valueOf(message), null, exception);
} }
} }
@ -478,36 +501,36 @@ public abstract class LogFactory {
this.logger = java.util.logging.Logger.getLogger(name); this.logger = java.util.logging.Logger.getLogger(name);
} }
public boolean isDebugEnabled() { public boolean isFatalEnabled() {
return this.logger.isLoggable(java.util.logging.Level.FINE); return isErrorEnabled();
} }
public boolean isErrorEnabled() { public boolean isErrorEnabled() {
return this.logger.isLoggable(java.util.logging.Level.SEVERE); return this.logger.isLoggable(java.util.logging.Level.SEVERE);
} }
public boolean isFatalEnabled() { public boolean isWarnEnabled() {
return this.logger.isLoggable(java.util.logging.Level.SEVERE); return this.logger.isLoggable(java.util.logging.Level.WARNING);
} }
public boolean isInfoEnabled() { public boolean isInfoEnabled() {
return this.logger.isLoggable(java.util.logging.Level.INFO); return this.logger.isLoggable(java.util.logging.Level.INFO);
} }
public boolean isTraceEnabled() { public boolean isDebugEnabled() {
return this.logger.isLoggable(java.util.logging.Level.FINEST); return this.logger.isLoggable(java.util.logging.Level.FINE);
} }
public boolean isWarnEnabled() { public boolean isTraceEnabled() {
return this.logger.isLoggable(java.util.logging.Level.WARNING); return this.logger.isLoggable(java.util.logging.Level.FINEST);
} }
public void debug(Object message) { public void fatal(Object message) {
log(java.util.logging.Level.FINE, message, null); error(message);
} }
public void debug(Object message, Throwable exception) { public void fatal(Object message, Throwable exception) {
log(java.util.logging.Level.FINE, message, exception); error(message, exception);
} }
public void error(Object message) { public void error(Object message) {
@ -518,12 +541,12 @@ public abstract class LogFactory {
log(java.util.logging.Level.SEVERE, message, exception); log(java.util.logging.Level.SEVERE, message, exception);
} }
public void fatal(Object message) { public void warn(Object message) {
error(message); log(java.util.logging.Level.WARNING, message, null);
} }
public void fatal(Object message, Throwable exception) { public void warn(Object message, Throwable exception) {
error(message, exception); log(java.util.logging.Level.WARNING, message, exception);
} }
public void info(Object message) { public void info(Object message) {
@ -534,29 +557,35 @@ public abstract class LogFactory {
log(java.util.logging.Level.INFO, message, exception); log(java.util.logging.Level.INFO, message, exception);
} }
public void trace(Object message) { public void debug(Object message) {
log(java.util.logging.Level.FINEST, message, null); log(java.util.logging.Level.FINE, message, null);
} }
public void trace(Object message, Throwable exception) { public void debug(Object message, Throwable exception) {
log(java.util.logging.Level.FINEST, message, exception); log(java.util.logging.Level.FINE, message, exception);
} }
public void warn(Object message) { public void trace(Object message) {
log(java.util.logging.Level.WARNING, message, null); log(java.util.logging.Level.FINEST, message, null);
} }
public void warn(Object message, Throwable exception) { public void trace(Object message, Throwable exception) {
log(java.util.logging.Level.WARNING, message, exception); log(java.util.logging.Level.FINEST, message, exception);
} }
private void log(java.util.logging.Level level, Object message, Throwable exception) { private void log(java.util.logging.Level level, Object message, Throwable exception) {
if (logger.isLoggable(level)) { if (logger.isLoggable(level)) {
LocationResolvingLogRecord rec = new LocationResolvingLogRecord(level, String.valueOf(message)); LogRecord rec;
rec.setLoggerName(this.name); if (message instanceof LogRecord) {
rec.setResourceBundleName(logger.getResourceBundleName()); rec = (LogRecord) message;
rec.setResourceBundle(logger.getResourceBundle()); }
rec.setThrown(exception); else {
rec = new LocationResolvingLogRecord(level, String.valueOf(message));
rec.setLoggerName(this.name);
rec.setResourceBundleName(logger.getResourceBundleName());
rec.setResourceBundle(logger.getResourceBundle());
rec.setThrown(exception);
}
logger.log(rec); logger.log(rec);
} }
} }
@ -568,7 +597,7 @@ public abstract class LogFactory {
@SuppressWarnings("serial") @SuppressWarnings("serial")
private static class LocationResolvingLogRecord extends java.util.logging.LogRecord { private static class LocationResolvingLogRecord extends LogRecord {
private static final String FQCN = JavaUtilLog.class.getName(); private static final String FQCN = JavaUtilLog.class.getName();
@ -622,8 +651,9 @@ public abstract class LogFactory {
setSourceMethodName(sourceMethodName); setSourceMethodName(sourceMethodName);
} }
@SuppressWarnings("deprecation") // setMillis is deprecated in JDK 9
protected Object writeReplace() { protected Object writeReplace() {
java.util.logging.LogRecord serialized = new java.util.logging.LogRecord(getLevel(), getMessage()); LogRecord serialized = new LogRecord(getLevel(), getMessage());
serialized.setLoggerName(getLoggerName()); serialized.setLoggerName(getLoggerName());
serialized.setResourceBundle(getResourceBundle()); serialized.setResourceBundle(getResourceBundle());
serialized.setResourceBundleName(getResourceBundleName()); serialized.setResourceBundleName(getResourceBundleName());

292
src/docs/asciidoc/overview.adoc

@ -41,9 +41,9 @@ Examples of how you, as an application developer, can benefit from the Spring pl
* Make a Java method execute in a database transaction without having to deal with * Make a Java method execute in a database transaction without having to deal with
transaction APIs. transaction APIs.
* Make a local Java method a remote procedure without having to deal with remote APIs. * Make a local Java method an HTTP endpoint without having to deal with the Servlet API.
* Make a local Java method a management operation without having to deal with JMX APIs. * Make a local Java method a message handler without having to deal with the JMS API.
* Make a local Java method a message handler without having to deal with JMS APIs. * Make a local Java method a management operation without having to deal with the JMX API.
@ -606,164 +606,55 @@ http://repo.spring.io/snapshot/org/springframework/spring[snapshots].
[[overview-logging]] [[overview-logging]]
==== Logging ==== Logging
Logging is a very important dependency for Spring because __a)__ it is the only mandatory Spring's logging setup has been revised for Spring 5: It is still based on the Apache
external dependency, __b)__ everyone likes to see some output from the tools they are Commons Logging API, also known as Jakarta Commons Logging (JCL). However, `spring-core`
using, and __c)__ Spring integrates with lots of other tools all of which have also made includes an embedded variant of Commons Logging now, with a Spring-specific `LogFactory`
a choice of logging dependency. One of the goals of an application developer is often to which automatically bridges to https://logging.apache.org/log4j/2.x/[Log4j 2],
have unified logging configured in a central place for the whole application, including http://www.slf4j.org[SLF4J], or the JDK's own `java.util.logging` (JUL). This
all external components. This is more difficult than it might have been since there are so implementation acts like the JCL-over-SLF4J bridge but with a range of dynamically
many choices of logging framework. detected providers, analogous to JBoss Logging's common targets (as used by Hibernate).
The mandatory logging dependency in Spring is the Jakarta Commons Logging API (JCL). We As a benefit, there is no need for external bridges like JCL-over-SLF4J anymore,
compile against JCL and we also make JCL `Log` objects visible for classes that extend and correspondingly no need for a manual exclude of the standard Commons Logging jar
the Spring Framework. It's important to users that all versions of Spring use the same from `spring-core` dependencies. Instead, it all just works in Spring's autodetection
logging library: migration is easy because backwards compatibility is preserved even style at runtime: Simply put Log4j 2.x or SLF4J on your classpath, without any extra
with applications that extend Spring. The way we do this is to make one of the modules bridge jars, or rely on default logging through JUL (with a customizable JUL setup).
in Spring depend explicitly on `commons-logging` (the canonical implementation of JCL), And nicely aligned, default Hibernate setup will choose the same common log targets.
and then make all the other modules depend on that at compile time. If you are using
Maven for example, and wondering where you picked up the dependency on If both Log4j and SLF4J are present, the Log4j API will be used preferably (since it
`commons-logging`, then it is from Spring and specifically from the central module directly matches JCL's signatures and natively supports a 'fatal' log level as well as
called `spring-core`. lazily resolved message objects), analogous to JBoss Logging's provider preferences.
Log4j may nevertheless be configured to delegate to SLF4J, or SLF4J may be configured
The nice thing about `commons-logging` is that you don't need anything else to make your to delegate to Log4j: Please check the instructions on their websites on how to arrive
application work. It has a runtime discovery algorithm that looks for other logging at a consistent outcome in such a mixed scenario.
frameworks in well known places on the classpath and uses one that it thinks is
appropriate (or you can tell it which one if you need to). If nothing else is available [TIP]
you get pretty nice looking logs just from the JDK (java.util.logging or JUL for short). ====
You should find that your Spring application works and logs happily to the console out As of Spring 5, drop any references to external Commons Logging bridges and also any
of the box in most situations, and that's important. manual exclude of the standard Commons Logging jar from your existing `spring-core`
dependency setup. Your Log4j or SLF4J or JUL setup will keep working without changes.
Note that you may still need a `commons-logging` exclude for other libraries (e.g.
[[overview-not-using-commons-logging]] Apache HttpClient, Castor, HtmlUnit) in order to pick up Spring's JCL bridge instead.
===== Not Using Commons Logging
Unfortunately, the runtime discovery algorithm in `commons-logging`, while convenient A custom `LogFactory` implementation at the Commons Logging level will NOT get picked
for the end-user, is problematic. If we could turn back the clock and start Spring now up since Spring's bridge does not support custom `commons-logging.properties' setup.
as a new project it would use a different logging dependency. The first choice would For any other log provider, please set up corresponding SLF4J or JUL bridges (which
probably be the Simple Logging Facade for Java ( http://www.slf4j.org[SLF4J]), which is you are very likely going to need for other libraries such as Hibernate anyway).
also used by a lot of other tools that people use with Spring inside their applications. Note that Log4j 1.x has reached its end-of-life; please migrate to Log4j 2.x.
====
There are basically two ways to switch off `commons-logging`:
. Exclude the dependency from the `spring-core` module (as it is the only module that
explicitly depends on `commons-logging`)
. Depend on a special `commons-logging` dependency that replaces the library with
an empty jar (more details can be found in the
http://slf4j.org/faq.html#excludingJCL[SLF4J FAQ])
To exclude commons-logging, add the following to your `dependencyManagement` section:
[source,xml,indent=0]
[subs="verbatim,quotes,attributes"]
----
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>{spring-version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
----
Now this application is probably broken because there is no implementation of the JCL
API on the classpath, so to fix it a new one has to be provided. In the next section we
show you how to provide an alternative implementation of JCL using SLF4J as an example.
[[overview-logging-slf4j]]
===== Using SLF4J
SLF4J is a cleaner dependency and more efficient at runtime than `commons-logging`
because it uses compile-time bindings instead of runtime discovery of the other logging
frameworks it integrates. This also means that you have to be more explicit about what
you want to happen at runtime, and declare it or configure it accordingly. SLF4J
provides bindings to many common logging frameworks, so you can usually choose one that
you already use, and bind to that for configuration and management.
SLF4J provides bindings to many common logging frameworks, including JCL, and it also
does the reverse: bridges between other logging frameworks and itself. So to use SLF4J
with Spring you need to replace the `commons-logging` dependency with the SLF4J-JCL
bridge. Once you have done that then logging calls from within Spring will be translated
into logging calls to the SLF4J API, so if other libraries in your application use that
API, then you have a single place to configure and manage logging.
A common choice might be to bridge Spring to SLF4J, and then provide explicit binding
from SLF4J to Log4j. You need to supply several dependencies (and exclude the existing
`commons-logging`): the bridge, the SLF4J implementation for Log4j, and the Log4j
implementation itself. In Maven you would do that like this:
[source,xml,indent=0]
[subs="verbatim,quotes,attributes"]
----
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>{spring-version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
----
That might seem like a lot of dependencies just to get some logging. Well it is, but it
__is__ optional, and it should behave better than the vanilla `commons-logging` with
respect to classloader issues, notably if you are in a strict container like an OSGi
platform. Allegedly there is also a performance benefit because the bindings are at
compile-time not runtime.
A more common choice amongst SLF4J users, which uses fewer steps and generates fewer
dependencies, is to bind directly to http://logback.qos.ch[Logback]. This removes the
extra binding step because Logback implements SLF4J directly, so you only need to depend
on two libraries not four ( `jcl-over-slf4j` and `logback`). If you do that you might
also need to exclude the slf4j-api dependency from other external dependencies (not
Spring), because you only want one version of that API on the classpath.
[[overview-logging-log4j]] [[overview-logging-log4j]]
===== Using Log4j ===== Using Log4j 2.x
NOTE: Log4j 1.x is EOL and the following applies to Log4j 2
Many people use http://logging.apache.org/log4j[Log4j] as a logging framework for http://logging.apache.org/log4j[Log4j 2] established itself as a fresh rewrite of
configuration and management purposes. It's efficient and well-established, and in fact the original Log4j project (1.x is EOL now). As of Spring 5, the embedded logging
it's what we use at runtime when we build and test Spring. Spring also provides some bridge will automatically delegate to Log4j 2.x when available on the classpath.
utilities for configuring and initializing Log4j, so it has an optional compile-time
dependency on Log4j in some modules.
To use Log4j with JCL, all you need to do is put Log4j on the classpath and provide So to use Log4j with Spring, all you need to do is put Log4j on the classpath and
it with a configuration file (`log4j2.xml`, `log4j2.properties`, or other provide it with a configuration file (`log4j2.xml`, `log4j2.properties`, or other
http://logging.apache.org/log4j/2.x/manual/configuration.html[supported configuration http://logging.apache.org/log4j/2.x/manual/configuration.html[supported configuration
formats]). For Maven users, the minimal dependencies needed are: formats]). For Maven users, the minimal dependency needed is:
[source,xml,indent=0] [source,xml,indent=0]
[subs="verbatim,quotes,attributes"] [subs="verbatim,quotes,attributes"]
@ -772,17 +663,13 @@ formats]). For Maven users, the minimal dependencies needed are:
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>2.7</version> <version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.7</version>
</dependency> </dependency>
</dependencies> </dependencies>
---- ----
If you also wish to use SLF4J, the following dependencies are also needed: If you also wish to enable SLF4J to delegate to Log4j, e.g. for other libraries
which use SLF4J by default, the following dependency is also needed:
[source,xml,indent=0] [source,xml,indent=0]
[subs="verbatim,quotes,attributes"] [subs="verbatim,quotes,attributes"]
@ -791,7 +678,7 @@ If you also wish to use SLF4J, the following dependencies are also needed:
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId> <artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version> <version>2.8.2</version>
</dependency> </dependency>
</dependencies> </dependencies>
---- ----
@ -817,25 +704,68 @@ Here is an example `log4j2.xml` for logging to the console:
</Configuration> </Configuration>
---- ----
[[overview-native-jcl]]
====== Runtime Containers with Native JCL
Many people run their Spring applications in a container that itself provides an
implementation of JCL. IBM Websphere Application Server (WAS) is the archetype. This
often causes problems, and unfortunately there is no silver bullet solution; simply
excluding `commons-logging` from your application is not enough in most situations.
To be clear about this: the problems reported are usually not with JCL per se, or even [[overview-logging-slf4j]]
with `commons-logging`: rather they are to do with binding `commons-logging` to another ===== Using SLF4J with Logback
framework (often Log4j). This can fail because `commons-logging` changed the way they do
the runtime discovery in between the older versions (1.0) found in some containers and
the modern versions that most people use now (1.1). Spring does not use any unusual
parts of the JCL API, so nothing breaks there, but as soon as Spring or your application
tries to do any logging you can find that the bindings to Log4j are not working.
In such cases with WAS the easiest thing to do is to invert the class loader hierarchy The Simple Logging Facade for Java (http://www.slf4j.org[SLF4J]) is a popular API
(IBM calls it "parent last") so that the application controls the JCL dependency, not used by other libraries commonly used with Spring. It is typically used with
the container. That option isn't always open, but there are plenty of other suggestions https://logback.qos.ch/[Logback] which is a native implementation of the SLF4J API
in the public domain for alternative approaches, and your mileage may vary depending on and therefore autodetected by Spring when added to the application classpath:
the exact version and feature set of the container.
[source,xml,indent=0]
[subs="verbatim,quotes,attributes"]
----
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
----
Alternatively, you may also configure SLF4J to delegate to Log4j (see above) or to
JUL, in particular for other libraries which use SLF4J by default. Note that it is
not important for all libraries to go through the same logging facade; it only
matters that they eventually delegate to the same log provider. So while Spring may
go to Log4j directly, other libraries may go through the SLF4J binding for Log4j,
or analogously for JUL.
[[overview-logging-jul]]
===== Using JUL (java.util.logging)
Spring will delegate to `java.util.logging` by default, provided that no Log4j or
SLF4J API is detected in the classpath. So there is no special dependency to set up:
just use Spring with no external dependency for log output to `java.util.logging`,
either in a standalone application (with a custom or default JUL setup at the JDK
level) or with an application server's log system (and its system-wide JUL setup).
Note that the `java.logging` module is NOT present by default on JDK 9, since it is
not included in `java.base`. This works fine when using Spring with Log4j or SLF4J
since the JUL API is not referenced in such a scenario. However, when choosing to
use JUL as a default log provider, remember to activate the `java.logging` module.
[[overview-logging-websphere]]
===== Commons Logging on WebSphere
Spring applications may run on a container that itself provides an implementation of
JCL, e.g. IBM's WebSphere Application Server (WAS). This does not cause issues per se
but leads to two different scenarios that need to be understood:
In a "parent first" ClassLoader delegation model (the default on WAS), applications
will always pick up the server-provided version of Commons Logging, delegating to the
WAS logging subsystem (which is actually based on JUL). An application-provided variant
of JCL, whether Spring 5's or the JCL-over-SLF4J bridge, will effectively be ignored,
along with any locally included log provider.
With a "parent last" delegation model (the default in a regular Servlet container but
an explicit configuration option on WAS), an application-provided Commons Logging
variant will be picked up, enabling you to set up a locally included log provider,
e.g. Log4j or Logback, within your application. In case of no local log provider,
Spring (like regular Commons Logging) will delegate to JUL by default, effectively
logging to WebSphere's logging subsystem like in the "parent first" scenario.
All in all, we recommend deploying Spring applications in the "parent last" model
since it naturally allows for local providers as well as the server's log subsystem.

Loading…
Cancel
Save