diff --git a/pom.xml b/pom.xml index 2700f64..95af143 100644 --- a/pom.xml +++ b/pom.xml @@ -407,7 +407,14 @@ under the License. ${failsafe.runorder} + + ch.qos.logback:* + org.apache.logging.log4j:* + org.slf4j:* + + org/apache/commons/logging/log4j2/** + org/apache/commons/logging/slf4j/** org/apache/commons/logging/serviceloader/** @@ -428,7 +435,7 @@ under the License. @@ -445,6 +452,92 @@ under the License. + + + log4j-test + + integration-test + + + + + org.apache.logging.log4j + log4j-core + ${log4j2.version} + + + + org/apache/commons/logging/log4j2/*TestCase.java + + + + org.apache.logging.log4j.core.impl.ReusableLogEventFactory + + + + + + slf4j-test + + integration-test + + + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + + org.apache.logging.log4j:* + + + org/apache/commons/logging/slf4j/*TestCase.java + + + + + + log4j-to-slf4j-test + + integration-test + + + + + org.apache.logging.log4j + log4j-to-slf4j + ${log4j2.version} + + + + org.slf4j + slf4j-api + + + + + + org.apache.logging.log4j:log4j-core + org.apache.logging.log4j:log4j-core-test + + + org/apache/commons/logging/slf4j/*TestCase.java + + + @@ -501,12 +594,24 @@ under the License. junit-vintage-engine test + + avalon-framework + avalon-framework + 4.1.5 + true + log4j log4j 1.2.17 true + + org.apache.logging.log4j + log4j-api + ${log4j2.version} + true + logkit logkit @@ -514,9 +619,9 @@ under the License. true - avalon-framework - avalon-framework - 4.1.5 + org.slf4j + slf4j-api + ${slf4j.version} true @@ -526,6 +631,37 @@ under the License. provided true + + org.apache.logging.log4j + log4j-core + ${log4j2.version} + test + + + org.apache.logging.log4j + log4j-core-test + ${log4j2.version} + test + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + ch.qos.logback + logback-core + ${logback.version} + test + + + ch.qos.logback + logback-core + ${logback.version} + test-jar + test + @@ -592,14 +728,22 @@ under the License. RC1 true + + 3.2.1 filesystem + + 2.21.1 + 1.3.11 + 2.0.9 javax.servlet;version="[2.1.0, 3.0.0)";resolution:=optional, org.apache.avalon.framework.logger;version="[4.1.3, 4.1.5]";resolution:=optional, org.apache.log;version="[1.0.1, 1.0.1]";resolution:=optional, - org.apache.log4j;version="[1.2.15, 2.0.0)";resolution:=optional + org.apache.log4j;version="[1.2.15, 2.0.0)";resolution:=optional, + org.apache.logging.log4j;version="[2.0, 4.0)";resolution:=optional, + org.slf4j;version="1.7.0, 3.0";resolution:=optional diff --git a/src/changes/changes.xml b/src/changes/changes.xml index cb4d250..e8e3765 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -44,7 +44,11 @@ The type attribute can be add,update,fix,remove. - + + + Add support for Log4j API and SLF4J #177. + + Add Automatic-Module-Name Manifest Header for Java 9 compatibility. @@ -72,7 +76,7 @@ The type attribute can be add,update,fix,remove. Fix possible NPEs in LogFactoryImpl. - + Bump Java from 6 to 8. diff --git a/src/main/java/org/apache/commons/logging/LogFactory.java b/src/main/java/org/apache/commons/logging/LogFactory.java index b4484ff..6d70d16 100644 --- a/src/main/java/org/apache/commons/logging/LogFactory.java +++ b/src/main/java/org/apache/commons/logging/LogFactory.java @@ -92,6 +92,13 @@ public abstract class LogFactory { */ public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory"; + private static final String FACTORY_LOG4J_API = "org.apache.commons.logging.impl.Log4jApiLogFactory"; + private static final String LOG4J_API_LOGGER = "org.apache.logging.log4j.Logger"; + private static final String LOG4J_TO_SLF4J_BRIDGE = "org.apache.logging.slf4j.SLF4JProvider"; + + private static final String FACTORY_SLF4J = "org.apache.commons.logging.impl.Slf4jLogFactory"; + private static final String SLF4J_API_LOGGER = "org.slf4j.Logger"; + /** * The fully qualified class name of the fallback {@code LogFactory} * implementation class to use, if no other can be found. @@ -932,7 +939,29 @@ public abstract class LogFactory { } } - // Fourth, try the fallback implementation class + // Fourth, try one of the 3 provided factories + + try { + // We prefer Log4j API, since it does not stringify objects. + if (factory == null && isClassAvailable(LOG4J_API_LOGGER, baseClassLoader)) { + // If the Log4j API is redirected to SLF4J, we use SLF4J directly. + if (isClassAvailable(LOG4J_TO_SLF4J_BRIDGE, baseClassLoader)) { + logDiagnostic( + "[LOOKUP] Log4j API to SLF4J redirection detected. Loading the SLF4J LogFactory implementation '" + FACTORY_SLF4J + "'."); + factory = newFactory(FACTORY_SLF4J, baseClassLoader, contextClassLoader); + } else { + logDiagnostic("[LOOKUP] Log4j API detected. Loading the Log4j API LogFactory implementation '" + FACTORY_LOG4J_API + "'."); + factory = newFactory(FACTORY_LOG4J_API, baseClassLoader, contextClassLoader); + } + } + + if (factory == null && isClassAvailable(SLF4J_API_LOGGER, baseClassLoader)) { + logDiagnostic("[LOOKUP] SLF4J detected. Loading the SLF4J LogFactory implementation '" + FACTORY_SLF4J + "'."); + factory = newFactory(FACTORY_SLF4J, baseClassLoader, contextClassLoader); + } + } catch (final Exception e) { + logDiagnostic("[LOOKUP] An exception occurred while creating LogFactory: " + e.getMessage()); + } if (factory == null) { if (isDiagnosticsEnabled()) { @@ -973,6 +1002,18 @@ public abstract class LogFactory { return factory; } + private static boolean isClassAvailable(final String className, final ClassLoader classLoader) { + final ClassLoader loader = LogFactory.class.getClassLoader(); + logDiagnostic("Checking if class '" + className + "' is available in class loader " + objectId(loader)); + try { + Class.forName(className, true, classLoader); + return true; + } catch (final ClassNotFoundException | LinkageError e) { + logDiagnostic("Failed to load class '" + className + "' from class loader " + objectId(loader) + ": " + e.getMessage()); + } + return false; + } + /** * Convenience method to return a named logger, without the application having to care about factories. * diff --git a/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java b/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java index f1a1309..90ffca4 100644 --- a/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java +++ b/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java @@ -40,6 +40,8 @@ import org.apache.log4j.Priority; * Log4J1.3 is expected to change Level so it no longer extends Priority, which is * a non-binary-compatible change. The class generated by compiling this code against * log4j 1.2 will therefore not run against log4j 1.3. + * + * @deprecated Scheduled for removal since version 1.x of Log4j has reached end-of-life. */ public class Log4JLogger implements Log, Serializable { diff --git a/src/main/java/org/apache/commons/logging/impl/Log4jApiLogFactory.java b/src/main/java/org/apache/commons/logging/impl/Log4jApiLogFactory.java new file mode 100644 index 0000000..0702db5 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/Log4jApiLogFactory.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.impl; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.spi.AbstractLoggerAdapter; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.spi.LoggerAdapter; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.util.StackLocatorUtil; + +/** + * Logger factory hardcoded to send everything to Log4j API. + *

+ * Based on the `log4j-jcl` artifact from Apache Logging Services. + *

+ * + * @since 1.3 + */ +public final class Log4jApiLogFactory extends LogFactory { + + private static final String[] EMPTY_ARRAY = new String[0]; + /** + * Marker used by all messages coming from Apache Commons Logging. + */ + private static final Marker MARKER = MarkerManager.getMarker("COMMONS-LOGGING"); + + /** + * Caches Log instances + */ + private final LoggerAdapter adapter = new LogAdapter(); + + private final ConcurrentMap attributes = new ConcurrentHashMap<>(); + + @Override + public Log getInstance(final String name) { + return adapter.getLogger(name); + } + + @Override + public Object getAttribute(final String name) { + return attributes.get(name); + } + + @Override + public String[] getAttributeNames() { + return attributes.keySet().toArray(EMPTY_ARRAY); + } + + @Override + public Log getInstance(final Class clazz) { + return getInstance(clazz.getName()); + } + + /** + * This method is supposed to clear all loggers. In this implementation it will clear all the logger + * wrappers but the loggers managed by the underlying logger context will not be. + */ + @Override + public void release() { + try { + adapter.close(); + } catch (final IOException ignored) { + } + } + + @Override + public void removeAttribute(final String name) { + attributes.remove(name); + } + + @Override + public void setAttribute(final String name, final Object value) { + if (value != null) { + attributes.put(name, value); + } else { + removeAttribute(name); + } + } + + private static final class LogAdapter extends AbstractLoggerAdapter { + + @Override + protected Log newLogger(final String name, final LoggerContext context) { + return new Log4j2Log(context.getLogger(name)); + } + + @Override + protected LoggerContext getContext() { + return getContext(LogManager.getFactory().isClassLoaderDependent() ? StackLocatorUtil.getCallerClass( + LogFactory.class) : null); + } + + } + + private static final class Log4j2Log implements Log { + + private static final String FQCN = Log4j2Log.class.getName(); + + private final ExtendedLogger logger; + + public Log4j2Log(final ExtendedLogger logger) { + this.logger = logger; + } + + @Override + public boolean isDebugEnabled() { + return isEnabled(Level.DEBUG); + } + + @Override + public boolean isErrorEnabled() { + return isEnabled(Level.ERROR); + } + + @Override + public boolean isFatalEnabled() { + return isEnabled(Level.FATAL); + } + + @Override + public boolean isInfoEnabled() { + return isEnabled(Level.INFO); + } + + @Override + public boolean isTraceEnabled() { + return isEnabled(Level.TRACE); + } + + @Override + public boolean isWarnEnabled() { + return isEnabled(Level.WARN); + } + + @Override + public void trace(final Object message) { + logIfEnabled(Level.TRACE, message, null); + } + + @Override + public void trace(final Object message, final Throwable t) { + logIfEnabled(Level.TRACE, message, t); + } + + @Override + public void debug(final Object message) { + logIfEnabled(Level.DEBUG, message, null); + } + + @Override + public void debug(final Object message, final Throwable t) { + logIfEnabled(Level.DEBUG, message, t); + } + + @Override + public void info(final Object message) { + logIfEnabled(Level.INFO, message, null); + } + + @Override + public void info(final Object message, final Throwable t) { + logIfEnabled(Level.INFO, message, t); + } + + @Override + public void warn(final Object message) { + logIfEnabled(Level.WARN, message, null); + } + + @Override + public void warn(final Object message, final Throwable t) { + logIfEnabled(Level.WARN, message, t); + } + + @Override + public void error(final Object message) { + logIfEnabled(Level.ERROR, message, null); + } + + @Override + public void error(final Object message, final Throwable t) { + logIfEnabled(Level.ERROR, message, t); + } + + @Override + public void fatal(final Object message) { + logIfEnabled(Level.FATAL, message, null); + } + + @Override + public void fatal(final Object message, final Throwable t) { + logIfEnabled(Level.FATAL, message, t); + } + + private boolean isEnabled(final Level level) { + return logger.isEnabled(level, MARKER, null); + } + + private void logIfEnabled(final Level level, final Object message, final Throwable t) { + if (message instanceof CharSequence) { + logger.logIfEnabled(FQCN, level, MARKER, (CharSequence) message, t); + } else { + logger.logIfEnabled(FQCN, level, MARKER, message, t); + } + } + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/Slf4jLogFactory.java b/src/main/java/org/apache/commons/logging/impl/Slf4jLogFactory.java new file mode 100644 index 0000000..33fa812 --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/Slf4jLogFactory.java @@ -0,0 +1,323 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.impl; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.slf4j.spi.LocationAwareLogger; + +import static org.slf4j.spi.LocationAwareLogger.DEBUG_INT; +import static org.slf4j.spi.LocationAwareLogger.ERROR_INT; +import static org.slf4j.spi.LocationAwareLogger.INFO_INT; +import static org.slf4j.spi.LocationAwareLogger.TRACE_INT; +import static org.slf4j.spi.LocationAwareLogger.WARN_INT; + +/** + * Logger factory hardcoded to send everything to SLF4J. + * + * @since 1.3 + */ +public final class Slf4jLogFactory extends LogFactory { + + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + /** + * Marker used by all messages coming from Apache Commons Logging. + */ + private static final Marker MARKER = MarkerFactory.getMarker("COMMONS-LOGGING"); + + /** + * Caches Log instances. + *

+ * The SLF4J reference implementation (Logback) has a single logger context, so each call to + * {@link #getInstance(String)} + * should give the same result. + *

+ */ + private final ConcurrentMap loggers = new ConcurrentHashMap<>(); + + private final ConcurrentMap attributes = new ConcurrentHashMap<>(); + + @Override + public Log getInstance(final String name) { + return loggers.computeIfAbsent(name, n -> { + final Logger logger = LoggerFactory.getLogger(n); + return logger instanceof LocationAwareLogger ? new Slf4jLocationAwareLog((LocationAwareLogger) logger) : new Slf4jLog( + logger); + }); + } + + @Override + public Object getAttribute(final String name) { + return attributes.get(name); + } + + @Override + public String[] getAttributeNames() { + return attributes.keySet().toArray(EMPTY_STRING_ARRAY); + } + + @Override + public Log getInstance(final Class clazz) throws LogConfigurationException { + return getInstance(clazz.getName()); + } + + /** + * This method is supposed to clear all loggers. + *

+ * In this implementation it calls a "stop" method if the logger factory supports it. This is the case of + * Logback. + *

+ */ + @Override + public void release() { + final ILoggerFactory factory = LoggerFactory.getILoggerFactory(); + try { + factory.getClass().getMethod("stop").invoke(factory); + } catch (final ReflectiveOperationException ignored) { + } + } + + @Override + public void removeAttribute(final String name) { + attributes.remove(name); + } + + @Override + public void setAttribute(final String name, final Object value) { + if (value != null) { + attributes.put(name, value); + } else { + removeAttribute(name); + } + } + + private static class Slf4jLog implements Log { + + private final Logger logger; + + public Slf4jLog(final Logger logger) { + this.logger = logger; + } + + @Override + public void debug(Object message) { + logger.debug(MARKER, String.valueOf(message)); + } + + @Override + public void debug(Object message, Throwable t) { + logger.debug(MARKER, String.valueOf(message), t); + } + + @Override + public void error(Object message) { + logger.error(MARKER, String.valueOf(message)); + } + + @Override + public void error(Object message, Throwable t) { + logger.debug(MARKER, String.valueOf(message), t); + } + + @Override + public void fatal(Object message) { + error(message); + } + + @Override + public void fatal(Object message, Throwable t) { + error(message, t); + } + + @Override + public void info(Object message) { + logger.info(MARKER, String.valueOf(message)); + } + + @Override + public void info(Object message, Throwable t) { + logger.info(MARKER, String.valueOf(message), t); + } + + @Override + public boolean isDebugEnabled() { + return logger.isDebugEnabled(MARKER); + } + + @Override + public boolean isErrorEnabled() { + return logger.isErrorEnabled(MARKER); + } + + @Override + public boolean isFatalEnabled() { + return isErrorEnabled(); + } + + @Override + public boolean isInfoEnabled() { + return logger.isInfoEnabled(MARKER); + } + + @Override + public boolean isTraceEnabled() { + return logger.isTraceEnabled(MARKER); + } + + @Override + public boolean isWarnEnabled() { + return logger.isWarnEnabled(MARKER); + } + + @Override + public void trace(Object message) { + logger.trace(MARKER, String.valueOf(message)); + } + + @Override + public void trace(Object message, Throwable t) { + logger.trace(MARKER, String.valueOf(message), t); + } + + @Override + public void warn(Object message) { + logger.warn(MARKER, String.valueOf(message)); + } + + @Override + public void warn(Object message, Throwable t) { + logger.warn(MARKER, String.valueOf(message), t); + } + } + + private static final class Slf4jLocationAwareLog implements Log { + + private static final String FQCN = Slf4jLocationAwareLog.class.getName(); + + private final LocationAwareLogger logger; + + public Slf4jLocationAwareLog(final LocationAwareLogger logger) { + this.logger = logger; + } + + @Override + public void debug(Object message) { + log(DEBUG_INT, message, null); + } + + @Override + public void debug(Object message, Throwable t) { + log(DEBUG_INT, message, t); + } + + @Override + public void error(Object message) { + log(ERROR_INT, message, null); + } + + @Override + public void error(Object message, Throwable t) { + log(ERROR_INT, message, t); + } + + @Override + public void fatal(Object message) { + error(message); + } + + @Override + public void fatal(Object message, Throwable t) { + error(message, t); + } + + @Override + public void info(Object message) { + log(INFO_INT, message, null); + } + + @Override + public void info(Object message, Throwable t) { + log(INFO_INT, message, t); + } + + + @Override + public boolean isDebugEnabled() { + return logger.isDebugEnabled(MARKER); + } + + @Override + public boolean isErrorEnabled() { + return logger.isErrorEnabled(MARKER); + } + + @Override + public boolean isFatalEnabled() { + return isErrorEnabled(); + } + + @Override + public boolean isInfoEnabled() { + return logger.isInfoEnabled(MARKER); + } + + @Override + public boolean isTraceEnabled() { + return logger.isTraceEnabled(MARKER); + } + + @Override + public boolean isWarnEnabled() { + return logger.isWarnEnabled(MARKER); + } + + @Override + public void trace(Object message) { + log(TRACE_INT, message, null); + } + + @Override + public void trace(Object message, Throwable t) { + log(TRACE_INT, message, t); + } + + @Override + public void warn(Object message) { + log(WARN_INT, message, null); + } + + @Override + public void warn(Object message, Throwable t) { + log(WARN_INT, message, t); + } + + private void log(final int level, final Object message, final Throwable t) { + logger.log(MARKER, FQCN, level, String.valueOf(message), EMPTY_OBJECT_ARRAY, t); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/log4j2/CallerInformationTestCase.java b/src/test/java/org/apache/commons/logging/log4j2/CallerInformationTestCase.java new file mode 100644 index 0000000..3ccf2df --- /dev/null +++ b/src/test/java/org/apache/commons/logging/log4j2/CallerInformationTestCase.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.log4j2; + +import java.util.List; + +import junit.framework.TestCase; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.impl.Log4jApiLogFactory; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.SimpleMessage; + +public class CallerInformationTestCase extends TestCase { + + private static final Object OBJ = new Object(); + private static final String STRING = "String"; + private static final Throwable T = new RuntimeException(); + private static final Marker MARKER = MarkerManager.getMarker("COMMONS-LOGGING"); + + private static final Level[] levels = {Level.FATAL, Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE}; + + private LogFactory factory; + private Log log; + private ListAppender appender; + + @Override + public void setUp() { + factory = LogFactory.getFactory(); + log = factory.getInstance(getClass()); + final LoggerContext context = LoggerContext.getContext(false); + final Configuration config = context.getConfiguration(); + appender = config.getAppender("LIST"); + assertNotNull("Missing Log4j 2.x appender.", appender); + } + + public void testFactoryClassName() { + assertEquals(Log4jApiLogFactory.class, factory.getClass()); + } + + public void testLocationInfo() { + appender.clear(); + // The following value must match the line number + final int currentLineNumber = 65; + log.fatal(OBJ); + log.fatal(OBJ, T); + log.error(OBJ); + log.error(OBJ, T); + log.warn(OBJ); + log.warn(OBJ, T); + log.info(OBJ); + log.info(OBJ, T); + log.debug(OBJ); + log.debug(OBJ, T); + log.trace(OBJ); + log.trace(OBJ, T); + final ObjectMessage expectedMessage = new ObjectMessage(OBJ); + final List events = appender.getEvents(); + assertEquals("All events received.", levels.length * 2, events.size()); + for (int lev = 0; lev < levels.length; lev++) { + for (int hasThrowable = 0; hasThrowable <= 1; hasThrowable++) { + final LogEvent event = events.get(2 * lev + hasThrowable); + assertEquals("Correct message.", expectedMessage, event.getMessage()); + assertEquals("Correct marker.", MARKER, event.getMarker()); + assertEquals("Level matches.", levels[lev], event.getLevel()); + final StackTraceElement location = event.getSource(); + assertNotNull("Has location", location); + assertEquals("Correct source file.", "CallerInformationTestCase.java", location.getFileName()); + assertEquals("Correct method name.", "testLocationInfo", location.getMethodName()); + assertEquals("Correct location class.", getClass().getName(), location.getClassName()); + assertEquals("Correct location line.", + currentLineNumber + 2 * lev + hasThrowable + 1, + location.getLineNumber()); + assertEquals("Correct exception", hasThrowable > 0 ? T : null, event.getThrown()); + } + } + } + + public void testMessageType() { + appender.clear(); + log.info(OBJ); + log.info(STRING); + final List events = appender.getEvents(); + assertEquals("Correct number of messages.", 2, events.size()); + assertEquals("Correct message type.", new ObjectMessage(OBJ), events.get(0).getMessage()); + assertEquals("Correct message type.", new SimpleMessage(STRING), events.get(1).getMessage()); + } +} diff --git a/src/test/java/org/apache/commons/logging/slf4j/CallerInformationTestCase.java b/src/test/java/org/apache/commons/logging/slf4j/CallerInformationTestCase.java new file mode 100644 index 0000000..e85511b --- /dev/null +++ b/src/test/java/org/apache/commons/logging/slf4j/CallerInformationTestCase.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging.slf4j; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.read.ListAppender; +import ch.qos.logback.core.spi.FilterReply; +import junit.framework.TestCase; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.impl.Slf4jLogFactory; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; + +public class CallerInformationTestCase extends TestCase { + + private static final String STRING = "String"; + private static final Throwable T = new RuntimeException(); + private static final List MARKERS = Collections.singletonList(MarkerFactory.getMarker("COMMONS-LOGGING")); + + private static final Level[] levels = {Level.ERROR, // SLF4J has no FATAL level + Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE}; + + private LogFactory factory; + private Log log; + private ListAppender appender; + + @Override + public void setUp() { + factory = LogFactory.getFactory(); + log = factory.getInstance(getClass()); + final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + final Logger logger = context.getLogger(Logger.ROOT_LOGGER_NAME); + appender = (ListAppender) logger.getAppender("LIST"); + appender.clearAllFilters(); + appender.addFilter(new Filter() { + @Override + public FilterReply decide(ILoggingEvent event) { + // Force the registration of caller data + event.getCallerData(); + return FilterReply.NEUTRAL; + } + }); + } + + public void testFactoryClassName() { + assertEquals(Slf4jLogFactory.class, factory.getClass()); + } + + public void testLocationInfo() { + appender.list.clear(); + // The following value must match the line number + final int currentLineNumber = 77; + log.fatal(STRING); + log.fatal(STRING, T); + log.error(STRING); + log.error(STRING, T); + log.warn(STRING); + log.warn(STRING, T); + log.info(STRING); + log.info(STRING, T); + log.debug(STRING); + log.debug(STRING, T); + log.trace(STRING); + log.trace(STRING, T); + final List events = new ArrayList<>(appender.list); + assertEquals("All events received.", levels.length * 2, events.size()); + for (int lev = 0; lev < levels.length; lev++) { + for (int hasThrowable = 0; hasThrowable <= 1; hasThrowable++) { + final ILoggingEvent event = events.get(2 * lev + hasThrowable); + assertEquals("Correct message.", STRING, event.getMessage()); + assertEquals("Correct marker.", MARKERS, event.getMarkerList()); + assertEquals("Level matches.", levels[lev], event.getLevel()); + final StackTraceElement[] callerData = event.getCallerData(); + assertTrue("Has location", callerData != null && callerData.length > 0); + final StackTraceElement location = callerData[0]; + assertEquals("Correct location class.", getClass().getName(), location.getClassName()); + assertEquals("Correct location line.", + currentLineNumber + 2 * lev + hasThrowable + 1, + location.getLineNumber()); + final ThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy(); + assertEquals("Correct exception", + hasThrowable > 0 ? T : null, + throwableProxy != null ? throwableProxy.getThrowable() : null); + } + } + } +} diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..8a0a7f0 --- /dev/null +++ b/src/test/resources/log4j2-test.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..10a8d3c --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file