diff options
Diffstat (limited to 'slf4j-api/src')
74 files changed, 4960 insertions, 1162 deletions
diff --git a/slf4j-api/src/main/java/org/slf4j/ILoggerFactory.java b/slf4j-api/src/main/java/org/slf4j/ILoggerFactory.java index 8ed82f3c..6fec242c 100644 --- a/slf4j-api/src/main/java/org/slf4j/ILoggerFactory.java +++ b/slf4j-api/src/main/java/org/slf4j/ILoggerFactory.java @@ -29,7 +29,7 @@ package org.slf4j; * instances by name. * * <p>Most users retrieve {@link Logger} instances through the static - * {@link LoggerFactory#getLogger(String)} method. An instance of of this + * {@link LoggerFactory#getLogger(String)} method. An instance of this * interface is bound internally with {@link LoggerFactory} class at * compile time. * diff --git a/slf4j-api/src/main/java/org/slf4j/Logger.java b/slf4j-api/src/main/java/org/slf4j/Logger.java index 93dd9945..2803f782 100644 --- a/slf4j-api/src/main/java/org/slf4j/Logger.java +++ b/slf4j-api/src/main/java/org/slf4j/Logger.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2004-2011 QOS.ch + * Copyright (c) 2004-2022 QOS.ch * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining @@ -25,12 +25,29 @@ package org.slf4j; +import static org.slf4j.event.EventConstants.DEBUG_INT; +import static org.slf4j.event.EventConstants.ERROR_INT; +import static org.slf4j.event.EventConstants.INFO_INT; +import static org.slf4j.event.EventConstants.TRACE_INT; +import static org.slf4j.event.EventConstants.WARN_INT; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.INFO; +import static org.slf4j.event.Level.TRACE; +import static org.slf4j.event.Level.WARN; + +import org.slf4j.event.Level; +import org.slf4j.helpers.CheckReturnValue; +import org.slf4j.spi.DefaultLoggingEventBuilder; +import org.slf4j.spi.LoggingEventBuilder; +import org.slf4j.spi.NOPLoggingEventBuilder; + /** * The org.slf4j.Logger interface is the main user entry point of SLF4J API. * It is expected that logging takes place through concrete implementations * of this interface. - * <p/> - * <h3>Typical usage pattern:</h3> + * + * <H3>Typical usage pattern:</H3> * <pre> * import org.slf4j.Logger; * import org.slf4j.LoggerFactory; @@ -45,26 +62,29 @@ package org.slf4j; * oldT = t; * t = temperature; * <span style="color:green">logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);</span> - * if(temperature.intValue() > 50) { + * if (temperature.intValue() > 50) { * <span style="color:green">logger.info("Temperature has risen above 50 degrees.");</span> * } * } * } * </pre> * - * Be sure to read the FAQ entry relating to <a href="../../../faq.html#logging_performance">parameterized + * <p>Note that version 2.0 of the SLF4J API introduces a <a href="../../../manual.html#fluent">fluent api</a>, + * the most significant API change to occur in the last 20 years. + * + * <p>Be sure to read the FAQ entry relating to <a href="../../../faq.html#logging_performance">parameterized * logging</a>. Note that logging statements can be parameterized in * <a href="../../../faq.html#paramException">presence of an exception/throwable</a>. * * <p>Once you are comfortable using loggers, i.e. instances of this interface, consider using - * <a href="MDC.html">MDC</a> as well as <a href="Marker.html">Markers</a>.</p> + * <a href="MDC.html">MDC</a> as well as <a href="Marker.html">Markers</a>. * * @author Ceki Gülcü */ public interface Logger { /** - * Case insensitive String constant used to retrieve the name of the root logger. + * Case-insensitive String constant used to retrieve the name of the root logger. * * @since 1.3 */ @@ -77,6 +97,70 @@ public interface Logger { public String getName(); /** + * <p>Make a new {@link LoggingEventBuilder} instance as appropriate for this logger implementation. + * This default implementation always returns a new instance of {@link DefaultLoggingEventBuilder}.</p> + * <p></p> + * <p>This method is intended to be used by logging systems implementing the SLF4J API and <b>not</b> + * by end users.</p> + * <p></p> + * <p>Also note that a {@link LoggingEventBuilder} instance should be built for all levels, + * independently of the level argument. In other words, this method is an <b>unconditional</b> + * constructor for the {@link LoggingEventBuilder} appropriate for this logger implementation.</p> + * <p></p> + * @param level desired level for the event builder + * @return a new {@link LoggingEventBuilder} instance as appropriate for <b>this</b> logger + * @since 2.0 + */ + default public LoggingEventBuilder makeLoggingEventBuilder(Level level) { + return new DefaultLoggingEventBuilder(this, level); + } + + /** + * Make a new {@link LoggingEventBuilder} instance as appropriate for this logger and the + * desired {@link Level} passed as parameter. If this Logger is disabled for the given Level, then + * a {@link NOPLoggingEventBuilder} is returned. + * + * + * @param level desired level for the event builder + * @return a new {@link LoggingEventBuilder} instance as appropriate for this logger + * @since 2.0 + */ + @CheckReturnValue + default public LoggingEventBuilder atLevel(Level level) { + if (isEnabledForLevel(level)) { + return makeLoggingEventBuilder(level); + } else { + return NOPLoggingEventBuilder.singleton(); + } + } + + + + /** + * Returns whether this Logger is enabled for a given {@link Level}. + * + * @param level + * @return true if enabled, false otherwise. + */ + default public boolean isEnabledForLevel(Level level) { + int levelInt = level.toInt(); + switch (levelInt) { + case (TRACE_INT): + return isTraceEnabled(); + case (DEBUG_INT): + return isDebugEnabled(); + case (INFO_INT): + return isInfoEnabled(); + case (WARN_INT): + return isWarnEnabled(); + case (ERROR_INT): + return isErrorEnabled(); + default: + throw new IllegalArgumentException("Level [" + level + "] not recognized."); + } + } + + /** * Is the logger instance enabled for the TRACE level? * * @return True if this Logger is enabled for the TRACE level, @@ -96,9 +180,9 @@ public interface Logger { /** * Log a message at the TRACE level according to the specified format * and argument. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the TRACE level. </p> + * is disabled for the TRACE level. * * @param format the format string * @param arg the argument @@ -109,9 +193,9 @@ public interface Logger { /** * Log a message at the TRACE level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the TRACE level. </p> + * is disabled for the TRACE level. * * @param format the format string * @param arg1 the first argument @@ -123,12 +207,12 @@ public interface Logger { /** * Log a message at the TRACE level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous string concatenation when the logger * is disabled for the TRACE level. However, this variant incurs the hidden * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method, * even if this logger is disabled for TRACE. The variants taking {@link #trace(String, Object) one} and - * {@link #trace(String, Object, Object) two} arguments exist solely in order to avoid this hidden cost.</p> + * {@link #trace(String, Object, Object) two} arguments exist solely in order to avoid this hidden cost. * * @param format the format string * @param arguments a list of 3 or more arguments @@ -159,6 +243,21 @@ public interface Logger { public boolean isTraceEnabled(Marker marker); /** + * Entry point for fluent-logging for {@link org.slf4j.event.Level#TRACE} level. + * + * @return LoggingEventBuilder instance as appropriate for level TRACE + * @since 2.0 + */ + @CheckReturnValue + default public LoggingEventBuilder atTrace() { + if (isTraceEnabled()) { + return makeLoggingEventBuilder(TRACE); + } else { + return NOPLoggingEventBuilder.singleton(); + } + } + + /** * Log a message with the specific Marker at the TRACE level. * * @param marker the marker data specific to this log statement @@ -232,9 +331,9 @@ public interface Logger { /** * Log a message at the DEBUG level according to the specified format * and argument. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the DEBUG level. </p> + * is disabled for the DEBUG level. * * @param format the format string * @param arg the argument @@ -244,9 +343,9 @@ public interface Logger { /** * Log a message at the DEBUG level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the DEBUG level. </p> + * is disabled for the DEBUG level. * * @param format the format string * @param arg1 the first argument @@ -257,13 +356,13 @@ public interface Logger { /** * Log a message at the DEBUG level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous string concatenation when the logger * is disabled for the DEBUG level. However, this variant incurs the hidden * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method, * even if this logger is disabled for DEBUG. The variants taking * {@link #debug(String, Object) one} and {@link #debug(String, Object, Object) two} - * arguments exist solely in order to avoid this hidden cost.</p> + * arguments exist solely in order to avoid this hidden cost. * * @param format the format string * @param arguments a list of 3 or more arguments @@ -341,6 +440,21 @@ public interface Logger { public void debug(Marker marker, String msg, Throwable t); /** + * Entry point for fluent-logging for {@link org.slf4j.event.Level#DEBUG} level. + * + * @return LoggingEventBuilder instance as appropriate for level DEBUG + * @since 2.0 + */ + @CheckReturnValue + default public LoggingEventBuilder atDebug() { + if (isDebugEnabled()) { + return makeLoggingEventBuilder(DEBUG); + } else { + return NOPLoggingEventBuilder.singleton(); + } + } + + /** * Is the logger instance enabled for the INFO level? * * @return True if this Logger is enabled for the INFO level, @@ -358,9 +472,9 @@ public interface Logger { /** * Log a message at the INFO level according to the specified format * and argument. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the INFO level. </p> + * is disabled for the INFO level. * * @param format the format string * @param arg the argument @@ -370,9 +484,9 @@ public interface Logger { /** * Log a message at the INFO level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the INFO level. </p> + * is disabled for the INFO level. * * @param format the format string * @param arg1 the first argument @@ -383,13 +497,13 @@ public interface Logger { /** * Log a message at the INFO level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous string concatenation when the logger * is disabled for the INFO level. However, this variant incurs the hidden * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method, * even if this logger is disabled for INFO. The variants taking * {@link #info(String, Object) one} and {@link #info(String, Object, Object) two} - * arguments exist solely in order to avoid this hidden cost.</p> + * arguments exist solely in order to avoid this hidden cost. * * @param format the format string * @param arguments a list of 3 or more arguments @@ -410,7 +524,8 @@ public interface Logger { * data is also taken into consideration. * * @param marker The marker data to take into consideration - * @return true if this logger is warn enabled, false otherwise + * @return true if this Logger is enabled for the INFO level, + * false otherwise. */ public boolean isInfoEnabled(Marker marker); @@ -466,6 +581,21 @@ public interface Logger { public void info(Marker marker, String msg, Throwable t); /** + * Entry point for fluent-logging for {@link org.slf4j.event.Level#INFO} level. + * + * @return LoggingEventBuilder instance as appropriate for level INFO + * @since 2.0 + */ + @CheckReturnValue + default public LoggingEventBuilder atInfo() { + if (isInfoEnabled()) { + return makeLoggingEventBuilder(INFO); + } else { + return NOPLoggingEventBuilder.singleton(); + } + } + + /** * Is the logger instance enabled for the WARN level? * * @return True if this Logger is enabled for the WARN level, @@ -483,9 +613,9 @@ public interface Logger { /** * Log a message at the WARN level according to the specified format * and argument. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the WARN level. </p> + * is disabled for the WARN level. * * @param format the format string * @param arg the argument @@ -495,13 +625,13 @@ public interface Logger { /** * Log a message at the WARN level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous string concatenation when the logger * is disabled for the WARN level. However, this variant incurs the hidden * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method, * even if this logger is disabled for WARN. The variants taking * {@link #warn(String, Object) one} and {@link #warn(String, Object, Object) two} - * arguments exist solely in order to avoid this hidden cost.</p> + * arguments exist solely in order to avoid this hidden cost. * * @param format the format string * @param arguments a list of 3 or more arguments @@ -511,9 +641,9 @@ public interface Logger { /** * Log a message at the WARN level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the WARN level. </p> + * is disabled for the WARN level. * * @param format the format string * @param arg1 the first argument @@ -592,6 +722,21 @@ public interface Logger { public void warn(Marker marker, String msg, Throwable t); /** + * Entry point for fluent-logging for {@link org.slf4j.event.Level#WARN} level. + * + * @return LoggingEventBuilder instance as appropriate for level WARN + * @since 2.0 + */ + @CheckReturnValue + default public LoggingEventBuilder atWarn() { + if (isWarnEnabled()) { + return makeLoggingEventBuilder(WARN); + } else { + return NOPLoggingEventBuilder.singleton(); + } + } + + /** * Is the logger instance enabled for the ERROR level? * * @return True if this Logger is enabled for the ERROR level, @@ -609,9 +754,9 @@ public interface Logger { /** * Log a message at the ERROR level according to the specified format * and argument. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the ERROR level. </p> + * is disabled for the ERROR level. * * @param format the format string * @param arg the argument @@ -621,9 +766,9 @@ public interface Logger { /** * Log a message at the ERROR level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous object creation when the logger - * is disabled for the ERROR level. </p> + * is disabled for the ERROR level. * * @param format the format string * @param arg1 the first argument @@ -634,13 +779,13 @@ public interface Logger { /** * Log a message at the ERROR level according to the specified format * and arguments. - * <p/> + * * <p>This form avoids superfluous string concatenation when the logger * is disabled for the ERROR level. However, this variant incurs the hidden * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method, * even if this logger is disabled for ERROR. The variants taking * {@link #error(String, Object) one} and {@link #error(String, Object, Object) two} - * arguments exist solely in order to avoid this hidden cost.</p> + * arguments exist solely in order to avoid this hidden cost. * * @param format the format string * @param arguments a list of 3 or more arguments @@ -718,4 +863,19 @@ public interface Logger { */ public void error(Marker marker, String msg, Throwable t); + /** + * Entry point for fluent-logging for {@link org.slf4j.event.Level#ERROR} level. + * + * @return LoggingEventBuilder instance as appropriate for level ERROR + * @since 2.0 + */ + @CheckReturnValue + default public LoggingEventBuilder atError() { + if (isErrorEnabled()) { + return makeLoggingEventBuilder(ERROR); + } else { + return NOPLoggingEventBuilder.singleton(); + } + } + } diff --git a/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java b/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java index 2f74c18b..4e1f0b0d 100755 --- a/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java +++ b/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java @@ -25,34 +25,41 @@ package org.slf4j; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; -import org.slf4j.helpers.NOPLoggerFactory; +import org.slf4j.event.SubstituteLoggingEvent; +import org.slf4j.helpers.NOP_FallbackServiceProvider; +import org.slf4j.helpers.Reporter; import org.slf4j.helpers.SubstituteLogger; -import org.slf4j.helpers.SubstituteLoggerFactory; +import org.slf4j.helpers.SubstituteServiceProvider; import org.slf4j.helpers.Util; -import org.slf4j.impl.StaticLoggerBinder; +import org.slf4j.spi.SLF4JServiceProvider; /** * The <code>LoggerFactory</code> is a utility class producing Loggers for - * various logging APIs, most notably for log4j, logback and JDK 1.4 logging. - * Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and - * {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported. - * <p/> - * <p/> - * <code>LoggerFactory</code> is essentially a wrapper around an - * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at - * compile time. - * <p/> - * <p/> - * Please note that all methods in <code>LoggerFactory</code> are static. + * various logging APIs, e.g. logback, reload4j, log4j and JDK 1.4 logging. + * Other implementations such as {@link org.slf4j.helpers.NOPLogger NOPLogger} and + * SimpleLogger are also supported. + * + * <p><code>LoggerFactory</code> is essentially a wrapper around an + * {@link ILoggerFactory} instance provided by a {@link SLF4JServiceProvider}. * + * <p> + * Please note that all methods in <code>LoggerFactory</code> are static. * * @author Alexander Dorokhine * @author Robert Elliot @@ -61,17 +68,27 @@ import org.slf4j.impl.StaticLoggerBinder; */ public final class LoggerFactory { - static final String CODES_PREFIX = "http://www.slf4j.org/codes.html"; + static final String CODES_PREFIX = "https://www.slf4j.org/codes.html"; + + static final String NO_PROVIDERS_URL = CODES_PREFIX + "#noProviders"; + static final String IGNORED_BINDINGS_URL = CODES_PREFIX + "#ignoredBindings"; - static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder"; static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings"; - static final String NULL_LF_URL = CODES_PREFIX + "#null_LF"; static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch"; static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger"; static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch"; + static final String REPLAY_URL = CODES_PREFIX + "#replay"; static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit"; - static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory could not be successfully initialized. See also " + UNSUCCESSFUL_INIT_URL; + static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also " + + UNSUCCESSFUL_INIT_URL; + /** + * System property for explicitly setting the provider class. If set and the provider could be instantiated, + * then the service loading mechanism will be bypassed. + * + * @since 2.0.9 + */ + static final public String PROVIDER_PROPERTY_KEY = "slf4j.provider"; static final int UNINITIALIZED = 0; static final int ONGOING_INITIALIZATION = 1; @@ -79,22 +96,69 @@ public final class LoggerFactory { static final int SUCCESSFUL_INITIALIZATION = 3; static final int NOP_FALLBACK_INITIALIZATION = 4; - static int INITIALIZATION_STATE = UNINITIALIZED; - static SubstituteLoggerFactory TEMP_FACTORY = new SubstituteLoggerFactory(); - static NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory(); + static volatile int INITIALIZATION_STATE = UNINITIALIZED; + static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider(); + static final NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider(); // Support for detecting mismatched logger names. static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch"; - static boolean DETECT_LOGGER_NAME_MISMATCH = Boolean.getBoolean(DETECT_LOGGER_NAME_MISMATCH_PROPERTY); + static final String JAVA_VENDOR_PROPERTY = "java.vendor.url"; + + static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY); + + static volatile SLF4JServiceProvider PROVIDER; + + // Package access for tests + static List<SLF4JServiceProvider> findServiceProviders() { + List<SLF4JServiceProvider> providerList = new ArrayList<>(); + + // retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that + // loaded the present class to search for services + final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader(); + + SLF4JServiceProvider explicitProvider = loadExplicitlySpecified(classLoaderOfLoggerFactory); + if(explicitProvider != null) { + providerList.add(explicitProvider); + return providerList; + } + + + ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory); + + Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator(); + while (iterator.hasNext()) { + safelyInstantiate(providerList, iterator); + } + return providerList; + } + + private static ServiceLoader<SLF4JServiceProvider> getServiceLoader(final ClassLoader classLoaderOfLoggerFactory) { + ServiceLoader<SLF4JServiceProvider> serviceLoader; + SecurityManager securityManager = System.getSecurityManager(); + if(securityManager == null) { + serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory); + } else { + final PrivilegedAction<ServiceLoader<SLF4JServiceProvider>> action = () -> ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory); + serviceLoader = AccessController.doPrivileged(action); + } + return serviceLoader; + } + + private static void safelyInstantiate(List<SLF4JServiceProvider> providerList, Iterator<SLF4JServiceProvider> iterator) { + try { + SLF4JServiceProvider provider = iterator.next(); + providerList.add(provider); + } catch (ServiceConfigurationError e) { + Reporter.error("A service provider failed to instantiate:\n" + e.getMessage()); + } + } /** * It is LoggerFactory's responsibility to track version changes and manage * the compatibility list. - * <p/> - * <p/> - * It is assumed that all versions in the 1.6 are mutually compatible. + * <p> */ - static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.6", "1.7" }; + static private final String[] API_COMPATIBILITY_LIST = new String[] { "2.0" }; // private constructor prevents instantiation private LoggerFactory() { @@ -102,18 +166,17 @@ public final class LoggerFactory { /** * Force LoggerFactory to consider itself uninitialized. - * <p/> - * <p/> + * <p> + * <p> * This method is intended to be called by classes (in the same package) for * testing purposes. This method is internal. It can be modified, renamed or * removed at any time without notice. - * <p/> - * <p/> + * <p> + * <p> * You are strongly discouraged from calling this method in production code. */ static void reset() { INITIALIZATION_STATE = UNINITIALIZED; - TEMP_FACTORY = new SubstituteLoggerFactory(); } private final static void performInitialization() { @@ -123,110 +186,77 @@ public final class LoggerFactory { } } - private static boolean messageContainsOrgSlf4jImplStaticLoggerBinder(String msg) { - if (msg == null) - return false; - if (msg.indexOf("org/slf4j/impl/StaticLoggerBinder") != -1) - return true; - if (msg.indexOf("org.slf4j.impl.StaticLoggerBinder") != -1) - return true; - return false; - } - private final static void bind() { try { - Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); - reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); - // the next line does the binding - StaticLoggerBinder.getSingleton(); - INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; - reportActualBinding(staticLoggerBinderPathSet); - fixSubstitutedLoggers(); - } catch (NoClassDefFoundError ncde) { - String msg = ncde.getMessage(); - if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { - INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; - Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); - Util.report("Defaulting to no-operation (NOP) logger implementation"); - Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); + List<SLF4JServiceProvider> providersList = findServiceProviders(); + reportMultipleBindingAmbiguity(providersList); + if (providersList != null && !providersList.isEmpty()) { + PROVIDER = providersList.get(0); + // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else. + PROVIDER.initialize(); + INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; + reportActualBinding(providersList); } else { - failedBinding(ncde); - throw ncde; - } - } catch (java.lang.NoSuchMethodError nsme) { - String msg = nsme.getMessage(); - if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) { - INITIALIZATION_STATE = FAILED_INITIALIZATION; - Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); - Util.report("Your binding is version 1.5.5 or earlier."); - Util.report("Upgrade your binding to version 1.6.x."); + INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; + Reporter.warn("No SLF4J providers were found."); + Reporter.warn("Defaulting to no-operation (NOP) logger implementation"); + Reporter.warn("See " + NO_PROVIDERS_URL + " for further details."); + + Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); + reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet); } - throw nsme; + postBindCleanUp(); } catch (Exception e) { failedBinding(e); throw new IllegalStateException("Unexpected initialization failure", e); } } - static void failedBinding(Throwable t) { - INITIALIZATION_STATE = FAILED_INITIALIZATION; - Util.report("Failed to instantiate SLF4J LoggerFactory", t); + static SLF4JServiceProvider loadExplicitlySpecified(ClassLoader classLoader) { + String explicitlySpecified = System.getProperty(PROVIDER_PROPERTY_KEY); + if (null == explicitlySpecified || explicitlySpecified.isEmpty()) { + return null; + } + try { + String message = String.format("Attempting to load provider \"%s\" specified via \"%s\" system property", explicitlySpecified, PROVIDER_PROPERTY_KEY); + Reporter.info(message); + Class<?> clazz = classLoader.loadClass(explicitlySpecified); + Constructor<?> constructor = clazz.getConstructor(); + Object provider = constructor.newInstance(); + return (SLF4JServiceProvider) provider; + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + String message = String.format("Failed to instantiate the specified SLF4JServiceProvider (%s)", explicitlySpecified); + Reporter.error(message, e); + return null; + } catch (ClassCastException e) { + String message = String.format("Specified SLF4JServiceProvider (%s) does not implement SLF4JServiceProvider interface", explicitlySpecified); + Reporter.error(message, e); + return null; + } } - private final static void fixSubstitutedLoggers() { - List<SubstituteLogger> loggers = TEMP_FACTORY.getLoggers(); - - if (loggers.isEmpty()) { + private static void reportIgnoredStaticLoggerBinders(Set<URL> staticLoggerBinderPathSet) { + if (staticLoggerBinderPathSet.isEmpty()) { return; } + Reporter.warn("Class path contains SLF4J bindings targeting slf4j-api versions 1.7.x or earlier."); - Util.report("The following set of substitute loggers may have been accessed"); - Util.report("during the initialization phase. Logging calls during this"); - Util.report("phase were not honored. However, subsequent logging calls to these"); - Util.report("loggers will work as normally expected."); - Util.report("See also " + SUBSTITUTE_LOGGER_URL); - for (SubstituteLogger subLogger : loggers) { - subLogger.setDelegate(getLogger(subLogger.getName())); - Util.report(subLogger.getName()); + for (URL path : staticLoggerBinderPathSet) { + Reporter.warn("Ignoring binding found at [" + path + "]"); } + Reporter.warn("See " + IGNORED_BINDINGS_URL + " for an explanation."); - TEMP_FACTORY.clear(); - } - - private final static void versionSanityCheck() { - try { - String requested = StaticLoggerBinder.REQUESTED_API_VERSION; - - boolean match = false; - for (int i = 0; i < API_COMPATIBILITY_LIST.length; i++) { - if (requested.startsWith(API_COMPATIBILITY_LIST[i])) { - match = true; - } - } - if (!match) { - Util.report("The requested version " + requested + " by your slf4j binding is not compatible with " - + Arrays.asList(API_COMPATIBILITY_LIST).toString()); - Util.report("See " + VERSION_MISMATCH + " for further details."); - } - } catch (java.lang.NoSuchFieldError nsfe) { - // given our large user base and SLF4J's commitment to backward - // compatibility, we cannot cry here. Only for implementations - // which willingly declare a REQUESTED_API_VERSION field do we - // emit compatibility warnings. - } catch (Throwable e) { - // we should never reach here - Util.report("Unexpected problem occured during version sanity check", e); - } } - // We need to use the name of the StaticLoggerBinder class, but we can't reference - // the class itself. - private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; + // We need to use the name of the StaticLoggerBinder class, but we can't + // reference the class itself. + private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; - private static Set<URL> findPossibleStaticLoggerBinderPathSet() { + static Set<URL> findPossibleStaticLoggerBinderPathSet() { // use Set instead of list in order to deal with bug #138 - // LinkedHashSet appropriate here because it preserves insertion order during iteration - Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); + // LinkedHashSet appropriate here because it preserves insertion order + // during iteration + Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<>(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); Enumeration<URL> paths; @@ -236,47 +266,155 @@ public final class LoggerFactory { paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); } while (paths.hasMoreElements()) { - URL path = (URL) paths.nextElement(); + URL path = paths.nextElement(); staticLoggerBinderPathSet.add(path); } } catch (IOException ioe) { - Util.report("Error getting resources from path", ioe); + Reporter.error("Error getting resources from path", ioe); } return staticLoggerBinderPathSet; } - private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> staticLoggerBinderPathSet) { - return staticLoggerBinderPathSet.size() > 1; + private static void postBindCleanUp() { + fixSubstituteLoggers(); + replayEvents(); + // release all resources in SUBST_FACTORY + SUBST_PROVIDER.getSubstituteLoggerFactory().clear(); + } + + private static void fixSubstituteLoggers() { + synchronized (SUBST_PROVIDER) { + SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization(); + for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) { + Logger logger = getLogger(substLogger.getName()); + substLogger.setDelegate(logger); + } + } + + } + + static void failedBinding(Throwable t) { + INITIALIZATION_STATE = FAILED_INITIALIZATION; + Reporter.error("Failed to instantiate SLF4J LoggerFactory", t); + } + + private static void replayEvents() { + final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue(); + final int queueSize = queue.size(); + int count = 0; + final int maxDrain = 128; + List<SubstituteLoggingEvent> eventList = new ArrayList<>(maxDrain); + while (true) { + int numDrained = queue.drainTo(eventList, maxDrain); + if (numDrained == 0) + break; + for (SubstituteLoggingEvent event : eventList) { + replaySingleEvent(event); + if (count++ == 0) + emitReplayOrSubstituionWarning(event, queueSize); + } + eventList.clear(); + } + } + + private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) { + if (event.getLogger().isDelegateEventAware()) { + emitReplayWarning(queueSize); + } else if (event.getLogger().isDelegateNOP()) { + // nothing to do + } else { + emitSubstitutionWarning(); + } + } + + private static void replaySingleEvent(SubstituteLoggingEvent event) { + if (event == null) + return; + + SubstituteLogger substLogger = event.getLogger(); + String loggerName = substLogger.getName(); + if (substLogger.isDelegateNull()) { + throw new IllegalStateException("Delegate logger cannot be null at this state."); + } + + if (substLogger.isDelegateNOP()) { + // nothing to do + } else if (substLogger.isDelegateEventAware()) { + if(substLogger.isEnabledForLevel(event.getLevel())) { + substLogger.log(event); + } + } else { + Reporter.warn(loggerName); + } + } + + private static void emitSubstitutionWarning() { + Reporter.warn("The following set of substitute loggers may have been accessed"); + Reporter.warn("during the initialization phase. Logging calls during this"); + Reporter.warn("phase were not honored. However, subsequent logging calls to these"); + Reporter.warn("loggers will work as normally expected."); + Reporter.warn("See also " + SUBSTITUTE_LOGGER_URL); + } + + private static void emitReplayWarning(int eventCount) { + Reporter.warn("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are"); + Reporter.warn("now being replayed. These are subject to the filtering rules of the underlying logging system."); + Reporter.warn("See also " + REPLAY_URL); + } + + private final static void versionSanityCheck() { + try { + String requested = PROVIDER.getRequestedApiVersion(); + + boolean match = false; + for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) { + if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) { + match = true; + } + } + if (!match) { + Reporter.warn("The requested version " + requested + " by your slf4j provider is not compatible with " + + Arrays.asList(API_COMPATIBILITY_LIST).toString()); + Reporter.warn("See " + VERSION_MISMATCH + " for further details."); + } + } catch (Throwable e) { + // we should never reach here + Reporter.error("Unexpected problem occurred during version sanity check", e); + } + } + + private static boolean isAmbiguousProviderList(List<SLF4JServiceProvider> providerList) { + return providerList.size() > 1; } /** - * Prints a warning message on the console if multiple bindings were found on the class path. - * No reporting is done otherwise. + * Prints a warning message on the console if multiple bindings were found + * on the class path. No reporting is done otherwise. * */ - private static void reportMultipleBindingAmbiguity(Set<URL> staticLoggerBinderPathSet) { - if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) { - Util.report("Class path contains multiple SLF4J bindings."); - Iterator<URL> iterator = staticLoggerBinderPathSet.iterator(); - while (iterator.hasNext()) { - URL path = (URL) iterator.next(); - Util.report("Found binding in [" + path + "]"); + private static void reportMultipleBindingAmbiguity(List<SLF4JServiceProvider> providerList) { + if (isAmbiguousProviderList(providerList)) { + Reporter.warn("Class path contains multiple SLF4J providers."); + for (SLF4JServiceProvider provider : providerList) { + Reporter.warn("Found provider [" + provider + "]"); } - Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); + Reporter.warn("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); } } - private static void reportActualBinding(Set<URL> staticLoggerBinderPathSet) { - if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) { - Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]"); + private static void reportActualBinding(List<SLF4JServiceProvider> providerList) { + // binderPathSet can be null under Android + if (!providerList.isEmpty() && isAmbiguousProviderList(providerList)) { + Reporter.info("Actual provider is of type [" + providerList.get(0) + "]"); } } /** - * Return a logger named according to the name parameter using the statically - * bound {@link ILoggerFactory} instance. + * Return a logger named according to the name parameter using the + * statically bound {@link ILoggerFactory} instance. * - * @param name The name of the logger. + * @param name + * The name of the logger. * @return logger */ public static Logger getLogger(String name) { @@ -285,29 +423,34 @@ public final class LoggerFactory { } /** - * Return a logger named corresponding to the class passed as parameter, using - * the statically bound {@link ILoggerFactory} instance. + * Return a logger named corresponding to the class passed as parameter, + * using the statically bound {@link ILoggerFactory} instance. + * + * <p> + * In case the <code>clazz</code> parameter differs from the name of the + * caller as computed internally by SLF4J, a logger name mismatch warning + * will be printed but only if the + * <code>slf4j.detectLoggerNameMismatch</code> system property is set to + * true. By default, this property is not set and no warnings will be + * printed even in case of a logger name mismatch. * - * <p>In case the the <code>clazz</code> parameter differs from the name of - * the caller as computed internally by SLF4J, a logger name mismatch warning will be - * printed but only if the <code>slf4j.detectLoggerNameMismatch</code> system property is - * set to true. By default, this property is not set and no warnings will be printed - * even in case of a logger name mismatch. - * - * @param clazz the returned logger will be named after clazz + * @param clazz + * the returned logger will be named after clazz * @return logger * * - * @see <a href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected logger name mismatch</a> + * @see <a + * href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected + * logger name mismatch</a> */ public static Logger getLogger(Class<?> clazz) { Logger logger = getLogger(clazz.getName()); if (DETECT_LOGGER_NAME_MISMATCH) { Class<?> autoComputedCallingClass = Util.getCallingClass(); - if (nonMatchingClasses(clazz, autoComputedCallingClass)) { - Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), + if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { + Reporter.warn(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName())); - Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); + Reporter.warn("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); } } return logger; @@ -319,28 +462,42 @@ public final class LoggerFactory { /** * Return the {@link ILoggerFactory} instance in use. - * <p/> - * <p/> + * <p> + * <p> * ILoggerFactory instance is bound with this class at compile time. * * @return the ILoggerFactory instance in use */ public static ILoggerFactory getILoggerFactory() { + return getProvider().getLoggerFactory(); + } + + /** + * Return the {@link SLF4JServiceProvider} in use. + + * @return provider in use + * @since 1.8.0 + */ + static SLF4JServiceProvider getProvider() { if (INITIALIZATION_STATE == UNINITIALIZED) { - INITIALIZATION_STATE = ONGOING_INITIALIZATION; - performInitialization(); + synchronized (LoggerFactory.class) { + if (INITIALIZATION_STATE == UNINITIALIZED) { + INITIALIZATION_STATE = ONGOING_INITIALIZATION; + performInitialization(); + } + } } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: - return StaticLoggerBinder.getSingleton().getLoggerFactory(); + return PROVIDER; case NOP_FALLBACK_INITIALIZATION: - return NOP_FALLBACK_FACTORY; + return NOP_FALLBACK_SERVICE_PROVIDER; case FAILED_INITIALIZATION: throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); case ONGOING_INITIALIZATION: // support re-entrant behavior. - // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106 - return TEMP_FACTORY; + // See also http://jira.qos.ch/browse/SLF4J-97 + return SUBST_PROVIDER; } throw new IllegalStateException("Unreachable code"); } diff --git a/slf4j-api/src/main/java/org/slf4j/LoggerFactoryFriend.java b/slf4j-api/src/main/java/org/slf4j/LoggerFactoryFriend.java new file mode 100755 index 00000000..06ca2017 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/LoggerFactoryFriend.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2004-2021 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j; + +/** + * All methods in this class are reserved for internal use, for testing purposes. + * + * <p>They can be modified, renamed or removed at any time without notice. + * + * <p>You are strongly discouraged calling any of the methods of this class. + * + * @since 1.8.0 + * + * @author Ceki Gülcü + */ +public class LoggerFactoryFriend { + + /* + * Force LoggerFactory to consider itself uninitialized. + */ + static public void reset() { + LoggerFactory.reset(); + } + + /** + * Set LoggerFactory.DETECT_LOGGER_NAME_MISMATCH variable. + * + * @param enabled a boolean + */ + public static void setDetectLoggerNameMismatch(boolean enabled) { + LoggerFactory.DETECT_LOGGER_NAME_MISMATCH = enabled; + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/MDC.java b/slf4j-api/src/main/java/org/slf4j/MDC.java index 06981772..6b0feded 100644 --- a/slf4j-api/src/main/java/org/slf4j/MDC.java +++ b/slf4j-api/src/main/java/org/slf4j/MDC.java @@ -25,13 +25,15 @@ package org.slf4j; import java.io.Closeable; +import java.util.Deque; import java.util.Map; -import org.slf4j.helpers.NOPMDCAdapter; import org.slf4j.helpers.BasicMDCAdapter; +import org.slf4j.helpers.NOPMDCAdapter; +import org.slf4j.helpers.Reporter; import org.slf4j.helpers.Util; -import org.slf4j.impl.StaticMDCBinder; import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; /** * This class hides and serves as a substitute for the underlying logging @@ -42,7 +44,7 @@ import org.slf4j.spi.MDCAdapter; * i.e. this class, will delegate to the underlying system's MDC. Note that at * this time, only two logging systems, namely log4j and logback, offer MDC * functionality. For java.util.logging which does not support MDC, - * {@link BasicMDCAdapter} will be used. For other systems, i.e slf4j-simple + * {@link BasicMDCAdapter} will be used. For other systems, i.e. slf4j-simple * and slf4j-nop, {@link NOPMDCAdapter} will be used. * * <p> @@ -64,6 +66,7 @@ import org.slf4j.spi.MDCAdapter; public class MDC { static final String NULL_MDCA_URL = "http://www.slf4j.org/codes.html#null_MDCA"; + private static final String MDC_APAPTER_CANNOT_BE_NULL_MESSAGE = "MDCAdapter cannot be null. See also " + NULL_MDCA_URL; static final String NO_STATIC_MDC_BINDER_URL = "http://www.slf4j.org/codes.html#no_static_mdc_binder"; static MDCAdapter mdcAdapter; @@ -86,21 +89,16 @@ public class MDC { } static { - try { - mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA(); - } catch (NoClassDefFoundError ncde) { + SLF4JServiceProvider provider = LoggerFactory.getProvider(); + if (provider != null) { + // obtain and attach the MDCAdapter from the provider + // If you wish to change the adapter, Setting the MDC.mdcAdapter variable might not be enough as + // the provider might perform additional assignments that you would need to replicate/adapt. + mdcAdapter = provider.getMDCAdapter(); + } else { + Reporter.error("Failed to find provider."); + Reporter.error("Defaulting to no-operation MDCAdapter implementation."); mdcAdapter = new NOPMDCAdapter(); - String msg = ncde.getMessage(); - if (msg != null && msg.indexOf("StaticMDCBinder") != -1) { - Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\"."); - Util.report("Defaulting to no-operation MDCAdapter implementation."); - Util.report("See " + NO_STATIC_MDC_BINDER_URL + " for further details."); - } else { - throw ncde; - } - } catch (Exception e) { - // we should never get here - Util.report("MDC binding unsuccessful.", e); } } @@ -124,7 +122,7 @@ public class MDC { throw new IllegalArgumentException("key parameter cannot be null"); } if (mdcAdapter == null) { - throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); } mdcAdapter.put(key, val); } @@ -169,7 +167,7 @@ public class MDC { * <p> * This method delegates all work to the MDC of the underlying logging system. * - * @param key + * @param key a key * @return the string value identified by the <code>key</code> parameter. * @throws IllegalArgumentException * in case the "key" parameter is null @@ -180,7 +178,7 @@ public class MDC { } if (mdcAdapter == null) { - throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); } return mdcAdapter.get(key); } @@ -191,7 +189,7 @@ public class MDC { * cannot be null. This method does nothing if there is no previous value * associated with <code>key</code>. * - * @param key + * @param key a key * @throws IllegalArgumentException * in case the "key" parameter is null */ @@ -201,7 +199,7 @@ public class MDC { } if (mdcAdapter == null) { - throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); } mdcAdapter.remove(key); } @@ -211,7 +209,7 @@ public class MDC { */ public static void clear() { if (mdcAdapter == null) { - throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); } mdcAdapter.clear(); } @@ -225,7 +223,7 @@ public class MDC { */ public static Map<String, String> getCopyOfContextMap() { if (mdcAdapter == null) { - throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); } return mdcAdapter.getCopyOfContextMap(); } @@ -235,13 +233,15 @@ public class MDC { * then copying the map passed as parameter. The context map passed as * parameter must only contain keys and values of type String. * + * Null valued argument is allowed (since SLF4J version 2.0.0). + * * @param contextMap * must contain only keys and values of type String * @since 1.5.1 */ public static void setContextMap(Map<String, String> contextMap) { if (mdcAdapter == null) { - throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); } mdcAdapter.setContextMap(contextMap); } @@ -256,4 +256,48 @@ public class MDC { return mdcAdapter; } + + + /** + * Push a value into the deque(stack) referenced by 'key'. + * + * @param key identifies the appropriate stack + * @param value the value to push into the stack + * @since 2.0.0 + */ + static public void pushByKey(String key, String value) { + if (mdcAdapter == null) { + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); + } + mdcAdapter.pushByKey(key, value); + } + + /** + * Pop the stack referenced by 'key' and return the value possibly null. + * + * @param key identifies the deque(stack) + * @return the value just popped. May be null/ + * @since 2.0.0 + */ + static public String popByKey(String key) { + if (mdcAdapter == null) { + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); + } + return mdcAdapter.popByKey(key); + } + + /** + * Returns a copy of the deque(stack) referenced by 'key'. May be null. + * + * @param key identifies the stack + * @return copy of stack referenced by 'key'. May be null. + * + * @since 2.0.0 + */ + public Deque<String> getCopyOfDequeByKey(String key) { + if (mdcAdapter == null) { + throw new IllegalStateException(MDC_APAPTER_CANNOT_BE_NULL_MESSAGE); + } + return mdcAdapter.getCopyOfDequeByKey(key); + } } diff --git a/slf4j-api/src/main/java/org/slf4j/Marker.java b/slf4j-api/src/main/java/org/slf4j/Marker.java index c6bfd5e2..4a6dd100 100644 --- a/slf4j-api/src/main/java/org/slf4j/Marker.java +++ b/slf4j-api/src/main/java/org/slf4j/Marker.java @@ -29,14 +29,16 @@ import java.util.Iterator; /** * Markers are named objects used to enrich log statements. Conforming logging - * system Implementations of SLF4J determine how information conveyed by markers - * are used, if at all. In particular, many conforming logging systems ignore - * marker data. - * - * <p> - * Markers can contain references to other markers, which in turn may contain - * references of their own. - * + * system implementations of SLF4J should determine how information conveyed by + * any markers are used, if at all. Many conforming logging systems ignore marker + * data entirely. + * + * <p>Markers can contain references to nested markers, which in turn may + * contain references of their own. Note that the fluent API (new in 2.0) allows adding + * multiple markers to a logging statement. It is often preferable to use + * multiple markers instead of nested markers. + * </p> + * * @author Ceki Gülcü */ public interface Marker extends Serializable { @@ -60,7 +62,11 @@ public interface Marker extends Serializable { /** * Add a reference to another Marker. - * + * + * <p>Note that the fluent API allows adding multiple markers to a logging statement. + * It is often preferable to use multiple markers instead of nested markers. + * </p> + * * @param reference * a reference to another marker * @throws IllegalArgumentException @@ -80,6 +86,7 @@ public interface Marker extends Serializable { /** * @deprecated Replaced by {@link #hasReferences()}. */ + @Deprecated public boolean hasChildren(); /** diff --git a/slf4j-api/src/main/java/org/slf4j/MarkerFactory.java b/slf4j-api/src/main/java/org/slf4j/MarkerFactory.java index 8d50698d..0b15d6ff 100644 --- a/slf4j-api/src/main/java/org/slf4j/MarkerFactory.java +++ b/slf4j-api/src/main/java/org/slf4j/MarkerFactory.java @@ -25,8 +25,9 @@ package org.slf4j; import org.slf4j.helpers.BasicMarkerFactory; +import org.slf4j.helpers.Reporter; import org.slf4j.helpers.Util; -import org.slf4j.impl.StaticMarkerBinder; +import org.slf4j.spi.SLF4JServiceProvider; /** * MarkerFactory is a utility class producing {@link Marker} instances as @@ -42,20 +43,20 @@ import org.slf4j.impl.StaticMarkerBinder; * @author Ceki Gülcü */ public class MarkerFactory { - static IMarkerFactory markerFactory; + static IMarkerFactory MARKER_FACTORY; private MarkerFactory() { } + // this is where the binding happens static { - try { - markerFactory = StaticMarkerBinder.SINGLETON.getMarkerFactory(); - } catch (NoClassDefFoundError e) { - markerFactory = new BasicMarkerFactory(); - - } catch (Exception e) { - // we should never get here - Util.report("Unexpected failure while binding MarkerFactory", e); + SLF4JServiceProvider provider = LoggerFactory.getProvider(); + if (provider != null) { + MARKER_FACTORY = provider.getMarkerFactory(); + } else { + Reporter.error("Failed to find provider"); + Reporter.error("Defaulting to BasicMarkerFactory."); + MARKER_FACTORY = new BasicMarkerFactory(); } } @@ -68,7 +69,7 @@ public class MarkerFactory { * @return marker */ public static Marker getMarker(String name) { - return markerFactory.getMarker(name); + return MARKER_FACTORY.getMarker(name); } /** @@ -79,7 +80,7 @@ public class MarkerFactory { * @since 1.5.1 */ public static Marker getDetachedMarker(String name) { - return markerFactory.getDetachedMarker(name); + return MARKER_FACTORY.getDetachedMarker(name); } /** @@ -91,6 +92,6 @@ public class MarkerFactory { * @return the IMarkerFactory instance in use */ public static IMarkerFactory getIMarkerFactory() { - return markerFactory; + return MARKER_FACTORY; } }
\ No newline at end of file diff --git a/slf4j-api/src/main/java/org/slf4j/event/DefaultLoggingEvent.java b/slf4j-api/src/main/java/org/slf4j/event/DefaultLoggingEvent.java new file mode 100755 index 00000000..5a101438 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/event/DefaultLoggingEvent.java @@ -0,0 +1,140 @@ +package org.slf4j.event; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.Marker; + +/** + * A default implementation of {@link LoggingEvent}. + * + * @author Ceki Gülcü + * + * @since 2.0.0 + */ +public class DefaultLoggingEvent implements LoggingEvent { + + Logger logger; + Level level; + + String message; + List<Marker> markers; + List<Object> arguments; + List<KeyValuePair> keyValuePairs; + + Throwable throwable; + String threadName; + long timeStamp; + + String callerBoundary; + + public DefaultLoggingEvent(Level level, Logger logger) { + this.logger = logger; + this.level = level; + } + + public void addMarker(Marker marker) { + if (markers == null) { + markers = new ArrayList<>(2); + } + markers.add(marker); + } + + @Override + public List<Marker> getMarkers() { + return markers; + } + + public void addArgument(Object p) { + getNonNullArguments().add(p); + } + + public void addArguments(Object... args) { + getNonNullArguments().addAll(Arrays.asList(args)); + } + + private List<Object> getNonNullArguments() { + if (arguments == null) { + arguments = new ArrayList<>(3); + } + return arguments; + } + + @Override + public List<Object> getArguments() { + return arguments; + } + + @Override + public Object[] getArgumentArray() { + if (arguments == null) + return null; + return arguments.toArray(); + } + + public void addKeyValue(String key, Object value) { + getNonnullKeyValuePairs().add(new KeyValuePair(key, value)); + } + + private List<KeyValuePair> getNonnullKeyValuePairs() { + if (keyValuePairs == null) { + keyValuePairs = new ArrayList<>(4); + } + return keyValuePairs; + } + + @Override + public List<KeyValuePair> getKeyValuePairs() { + return keyValuePairs; + } + + public void setThrowable(Throwable cause) { + this.throwable = cause; + } + + @Override + public Level getLevel() { + return level; + } + + @Override + public String getLoggerName() { + return logger.getName(); + } + + @Override + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public Throwable getThrowable() { + return throwable; + } + + public String getThreadName() { + return threadName; + } + + public long getTimeStamp() { + return timeStamp; + } + + public void setTimeStamp(long timeStamp) { + this.timeStamp = timeStamp; + } + + public void setCallerBoundary(String fqcn) { + this.callerBoundary = fqcn; + } + + public String getCallerBoundary() { + return callerBoundary; + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/event/EventConstants.java b/slf4j-api/src/main/java/org/slf4j/event/EventConstants.java new file mode 100755 index 00000000..00975da2 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/event/EventConstants.java @@ -0,0 +1,13 @@ +package org.slf4j.event; + +import org.slf4j.spi.LocationAwareLogger; + +public class EventConstants { + public static final int ERROR_INT = LocationAwareLogger.ERROR_INT; + public static final int WARN_INT = LocationAwareLogger.WARN_INT; + public static final int INFO_INT = LocationAwareLogger.INFO_INT; + public static final int DEBUG_INT = LocationAwareLogger.DEBUG_INT; + public static final int TRACE_INT = LocationAwareLogger.TRACE_INT; + public static final String NA_SUBST = "NA/SubstituteLogger"; + +} diff --git a/slf4j-api/src/main/java/org/slf4j/event/EventRecordingLogger.java b/slf4j-api/src/main/java/org/slf4j/event/EventRecordingLogger.java new file mode 100755 index 00000000..b21a2e45 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/event/EventRecordingLogger.java @@ -0,0 +1,84 @@ +package org.slf4j.event; + +import java.util.Queue; + +import org.slf4j.Marker; +import org.slf4j.helpers.LegacyAbstractLogger; +import org.slf4j.helpers.SubstituteLogger; + +/** + * + * This class is used to record events during the initialization phase of the + * underlying logging framework. It is called by {@link SubstituteLogger}. + * + * + * @author Ceki Gülcü + * @author Wessel van Norel + * + */ +public class EventRecordingLogger extends LegacyAbstractLogger { + + private static final long serialVersionUID = -176083308134819629L; + + String name; + SubstituteLogger logger; + Queue<SubstituteLoggingEvent> eventQueue; + + // as an event recording logger we have no choice but to record all events + final static boolean RECORD_ALL_EVENTS = true; + + public EventRecordingLogger(SubstituteLogger logger, Queue<SubstituteLoggingEvent> eventQueue) { + this.logger = logger; + this.name = logger.getName(); + this.eventQueue = eventQueue; + } + + public String getName() { + return name; + } + + public boolean isTraceEnabled() { + return RECORD_ALL_EVENTS; + } + + public boolean isDebugEnabled() { + return RECORD_ALL_EVENTS; + } + + public boolean isInfoEnabled() { + return RECORD_ALL_EVENTS; + } + + public boolean isWarnEnabled() { + return RECORD_ALL_EVENTS; + } + + public boolean isErrorEnabled() { + return RECORD_ALL_EVENTS; + } + + // WARNING: this method assumes that any throwable is properly extracted + protected void handleNormalizedLoggingCall(Level level, Marker marker, String msg, Object[] args, Throwable throwable) { + SubstituteLoggingEvent loggingEvent = new SubstituteLoggingEvent(); + loggingEvent.setTimeStamp(System.currentTimeMillis()); + loggingEvent.setLevel(level); + loggingEvent.setLogger(logger); + loggingEvent.setLoggerName(name); + if (marker != null) { + loggingEvent.addMarker(marker); + } + loggingEvent.setMessage(msg); + loggingEvent.setThreadName(Thread.currentThread().getName()); + + loggingEvent.setArgumentArray(args); + loggingEvent.setThrowable(throwable); + + eventQueue.add(loggingEvent); + + } + + @Override + protected String getFullyQualifiedCallerName() { + return null; + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/event/KeyValuePair.java b/slf4j-api/src/main/java/org/slf4j/event/KeyValuePair.java new file mode 100755 index 00000000..9aebe198 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/event/KeyValuePair.java @@ -0,0 +1,32 @@ +package org.slf4j.event; + +import java.util.Objects; + +public class KeyValuePair { + + public final String key; + public final Object value; + + public KeyValuePair(String key, Object value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(key) + "=\"" + String.valueOf(value) +"\""; + } + + @Override + public boolean equals(Object o) { + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; + KeyValuePair that = (KeyValuePair) o; + return Objects.equals(key, that.key) && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/event/Level.java b/slf4j-api/src/main/java/org/slf4j/event/Level.java new file mode 100755 index 00000000..a288b06b --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/event/Level.java @@ -0,0 +1,56 @@ +package org.slf4j.event; + +import static org.slf4j.event.EventConstants.DEBUG_INT; +import static org.slf4j.event.EventConstants.ERROR_INT; +import static org.slf4j.event.EventConstants.INFO_INT; +import static org.slf4j.event.EventConstants.TRACE_INT; +import static org.slf4j.event.EventConstants.WARN_INT; + +/** + * SLF4J's internal representation of Level. + * + * + * @author Ceki Gülcü + * @since 1.7.15 + */ +public enum Level { + + ERROR(ERROR_INT, "ERROR"), WARN(WARN_INT, "WARN"), INFO(INFO_INT, "INFO"), DEBUG(DEBUG_INT, "DEBUG"), TRACE(TRACE_INT, "TRACE"); + + private final int levelInt; + private final String levelStr; + + Level(int i, String s) { + levelInt = i; + levelStr = s; + } + + public int toInt() { + return levelInt; + } + + public static Level intToLevel(int levelInt) { + switch (levelInt) { + case (TRACE_INT): + return TRACE; + case (DEBUG_INT): + return DEBUG; + case (INFO_INT): + return INFO; + case (WARN_INT): + return WARN; + case (ERROR_INT): + return ERROR; + default: + throw new IllegalArgumentException("Level integer [" + levelInt + "] not recognized."); + } + } + + /** + * Returns the string representation of this Level. + */ + public String toString() { + return levelStr; + } + +} diff --git a/slf4j-api/src/main/java/org/slf4j/event/LoggingEvent.java b/slf4j-api/src/main/java/org/slf4j/event/LoggingEvent.java new file mode 100755 index 00000000..27bdf3e2 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/event/LoggingEvent.java @@ -0,0 +1,45 @@ +package org.slf4j.event; + +import java.util.List; + +import org.slf4j.Marker; + +/** + * The minimal interface sufficient for the restitution of data passed + * by the user to the SLF4J API. + * + * @author Ceki Gülcü + * @since 1.7.15 + */ +public interface LoggingEvent { + + Level getLevel(); + + String getLoggerName(); + + String getMessage(); + + List<Object> getArguments(); + + Object[] getArgumentArray(); + + List<Marker> getMarkers(); + + List<KeyValuePair> getKeyValuePairs(); + + Throwable getThrowable(); + + long getTimeStamp(); + + String getThreadName(); + + /** + * Returns the presumed caller boundary provided by the logging library (not the user of the library). + * Null by default. + * + * @return presumed caller, null by default. + */ + default String getCallerBoundary() { + return null; + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/event/SubstituteLoggingEvent.java b/slf4j-api/src/main/java/org/slf4j/event/SubstituteLoggingEvent.java new file mode 100755 index 00000000..4c8f7ad7 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/event/SubstituteLoggingEvent.java @@ -0,0 +1,115 @@ +package org.slf4j.event; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Marker; +import org.slf4j.helpers.SubstituteLogger; + +public class SubstituteLoggingEvent implements LoggingEvent { + + Level level; + List<Marker> markers; + String loggerName; + SubstituteLogger logger; + String threadName; + String message; + Object[] argArray; + List<KeyValuePair> keyValuePairList; + + long timeStamp; + Throwable throwable; + + public Level getLevel() { + return level; + } + + public void setLevel(Level level) { + this.level = level; + } + + public List<Marker> getMarkers() { + return markers; + } + + public void addMarker(Marker marker) { + if (marker == null) + return; + + if (markers == null) { + markers = new ArrayList<>(2); + } + + markers.add(marker); + } + + public String getLoggerName() { + return loggerName; + } + + public void setLoggerName(String loggerName) { + this.loggerName = loggerName; + } + + public SubstituteLogger getLogger() { + return logger; + } + + public void setLogger(SubstituteLogger logger) { + this.logger = logger; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Object[] getArgumentArray() { + return argArray; + } + + public void setArgumentArray(Object[] argArray) { + this.argArray = argArray; + } + + @Override + public List<Object> getArguments() { + if (argArray == null) { + return null; + } + return Arrays.asList(argArray); + } + + public long getTimeStamp() { + return timeStamp; + } + + public void setTimeStamp(long timeStamp) { + this.timeStamp = timeStamp; + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + public Throwable getThrowable() { + return throwable; + } + + public void setThrowable(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public List<KeyValuePair> getKeyValuePairs() { + return keyValuePairList; + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/AbstractLogger.java b/slf4j-api/src/main/java/org/slf4j/helpers/AbstractLogger.java new file mode 100644 index 00000000..0caaf810 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/AbstractLogger.java @@ -0,0 +1,423 @@ +/** + * Copyright (c) 2004-2019 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.helpers; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.event.Level; + +/** + * An abstract implementation which delegates actual logging work to the + * {@link #handleNormalizedLoggingCall(Level, Marker, String, Object[], Throwable)} method. + * + * @author Ceki Gülcü + * @since 2.0 + */ +public abstract class AbstractLogger implements Logger, Serializable { + + private static final long serialVersionUID = -2529255052481744503L; + + protected String name; + + public String getName() { + return name; + } + + /** + * Replace this instance with a homonymous (same name) logger returned + * by LoggerFactory. Note that this method is only called during + * deserialization. + * + * <p> + * This approach will work well if the desired ILoggerFactory is the one + * referenced by {@link org.slf4j.LoggerFactory} However, if the user manages its logger hierarchy + * through a different (non-static) mechanism, e.g. dependency injection, then + * this approach would be mostly counterproductive. + * + * @return logger with same name as returned by LoggerFactory + * @throws ObjectStreamException + */ + protected Object readResolve() throws ObjectStreamException { + // using getName() instead of this.name works even for + // NOPLogger + return LoggerFactory.getLogger(getName()); + } + + @Override + public void trace(String msg) { + if (isTraceEnabled()) { + handle_0ArgsCall(Level.TRACE, null, msg, null); + } + } + + @Override + public void trace(String format, Object arg) { + if (isTraceEnabled()) { + handle_1ArgsCall(Level.TRACE, null, format, arg); + } + } + + @Override + public void trace(String format, Object arg1, Object arg2) { + if (isTraceEnabled()) { + handle2ArgsCall(Level.TRACE, null, format, arg1, arg2); + } + } + + @Override + public void trace(String format, Object... arguments) { + if (isTraceEnabled()) { + handleArgArrayCall(Level.TRACE, null, format, arguments); + } + } + + @Override + public void trace(String msg, Throwable t) { + if (isTraceEnabled()) { + handle_0ArgsCall(Level.TRACE, null, msg, t); + } + } + + @Override + public void trace(Marker marker, String msg) { + if (isTraceEnabled(marker)) { + handle_0ArgsCall(Level.TRACE, marker, msg, null); + } + } + + @Override + public void trace(Marker marker, String format, Object arg) { + if (isTraceEnabled(marker)) { + handle_1ArgsCall(Level.TRACE, marker, format, arg); + } + } + + @Override + public void trace(Marker marker, String format, Object arg1, Object arg2) { + if (isTraceEnabled(marker)) { + handle2ArgsCall(Level.TRACE, marker, format, arg1, arg2); + } + } + + @Override + public void trace(Marker marker, String format, Object... argArray) { + if (isTraceEnabled(marker)) { + handleArgArrayCall(Level.TRACE, marker, format, argArray); + } + } + + public void trace(Marker marker, String msg, Throwable t) { + if (isTraceEnabled(marker)) { + handle_0ArgsCall(Level.TRACE, marker, msg, t); + } + } + + public void debug(String msg) { + if (isDebugEnabled()) { + handle_0ArgsCall(Level.DEBUG, null, msg, null); + } + } + + public void debug(String format, Object arg) { + if (isDebugEnabled()) { + handle_1ArgsCall(Level.DEBUG, null, format, arg); + } + } + + public void debug(String format, Object arg1, Object arg2) { + if (isDebugEnabled()) { + handle2ArgsCall(Level.DEBUG, null, format, arg1, arg2); + } + } + + public void debug(String format, Object... arguments) { + if (isDebugEnabled()) { + handleArgArrayCall(Level.DEBUG, null, format, arguments); + } + } + + public void debug(String msg, Throwable t) { + if (isDebugEnabled()) { + handle_0ArgsCall(Level.DEBUG, null, msg, t); + } + } + + public void debug(Marker marker, String msg) { + if (isDebugEnabled(marker)) { + handle_0ArgsCall(Level.DEBUG, marker, msg, null); + } + } + + public void debug(Marker marker, String format, Object arg) { + if (isDebugEnabled(marker)) { + handle_1ArgsCall(Level.DEBUG, marker, format, arg); + } + } + + public void debug(Marker marker, String format, Object arg1, Object arg2) { + if (isDebugEnabled(marker)) { + handle2ArgsCall(Level.DEBUG, marker, format, arg1, arg2); + } + } + + public void debug(Marker marker, String format, Object... arguments) { + if (isDebugEnabled(marker)) { + handleArgArrayCall(Level.DEBUG, marker, format, arguments); + } + } + + public void debug(Marker marker, String msg, Throwable t) { + if (isDebugEnabled(marker)) { + handle_0ArgsCall(Level.DEBUG, marker, msg, t); + } + } + + public void info(String msg) { + if (isInfoEnabled()) { + handle_0ArgsCall(Level.INFO, null, msg, null); + } + } + + public void info(String format, Object arg) { + if (isInfoEnabled()) { + handle_1ArgsCall(Level.INFO, null, format, arg); + } + } + + public void info(String format, Object arg1, Object arg2) { + if (isInfoEnabled()) { + handle2ArgsCall(Level.INFO, null, format, arg1, arg2); + } + } + + public void info(String format, Object... arguments) { + if (isInfoEnabled()) { + handleArgArrayCall(Level.INFO, null, format, arguments); + } + } + + public void info(String msg, Throwable t) { + if (isInfoEnabled()) { + handle_0ArgsCall(Level.INFO, null, msg, t); + } + } + + public void info(Marker marker, String msg) { + if (isInfoEnabled(marker)) { + handle_0ArgsCall(Level.INFO, marker, msg, null); + } + } + + public void info(Marker marker, String format, Object arg) { + if (isInfoEnabled(marker)) { + handle_1ArgsCall(Level.INFO, marker, format, arg); + } + } + + public void info(Marker marker, String format, Object arg1, Object arg2) { + if (isInfoEnabled(marker)) { + handle2ArgsCall(Level.INFO, marker, format, arg1, arg2); + } + } + + public void info(Marker marker, String format, Object... arguments) { + if (isInfoEnabled(marker)) { + handleArgArrayCall(Level.INFO, marker, format, arguments); + } + } + + public void info(Marker marker, String msg, Throwable t) { + if (isInfoEnabled(marker)) { + handle_0ArgsCall(Level.INFO, marker, msg, t); + } + } + + public void warn(String msg) { + if (isWarnEnabled()) { + handle_0ArgsCall(Level.WARN, null, msg, null); + } + } + + public void warn(String format, Object arg) { + if (isWarnEnabled()) { + handle_1ArgsCall(Level.WARN, null, format, arg); + } + } + + public void warn(String format, Object arg1, Object arg2) { + if (isWarnEnabled()) { + handle2ArgsCall(Level.WARN, null, format, arg1, arg2); + } + } + + public void warn(String format, Object... arguments) { + if (isWarnEnabled()) { + handleArgArrayCall(Level.WARN, null, format, arguments); + } + } + + public void warn(String msg, Throwable t) { + if (isWarnEnabled()) { + handle_0ArgsCall(Level.WARN, null, msg, t); + } + } + + public void warn(Marker marker, String msg) { + if (isWarnEnabled(marker)) { + handle_0ArgsCall(Level.WARN, marker, msg, null); + } + } + + public void warn(Marker marker, String format, Object arg) { + if (isWarnEnabled(marker)) { + handle_1ArgsCall(Level.WARN, marker, format, arg); + } + } + + public void warn(Marker marker, String format, Object arg1, Object arg2) { + if (isWarnEnabled(marker)) { + handle2ArgsCall(Level.WARN, marker, format, arg1, arg2); + } + } + + public void warn(Marker marker, String format, Object... arguments) { + if (isWarnEnabled(marker)) { + handleArgArrayCall(Level.WARN, marker, format, arguments); + } + } + + public void warn(Marker marker, String msg, Throwable t) { + if (isWarnEnabled(marker)) { + handle_0ArgsCall(Level.WARN, marker, msg, t); + } + } + + public void error(String msg) { + if (isErrorEnabled()) { + handle_0ArgsCall(Level.ERROR, null, msg, null); + } + } + + public void error(String format, Object arg) { + if (isErrorEnabled()) { + handle_1ArgsCall(Level.ERROR, null, format, arg); + } + } + + public void error(String format, Object arg1, Object arg2) { + if (isErrorEnabled()) { + handle2ArgsCall(Level.ERROR, null, format, arg1, arg2); + } + } + + public void error(String format, Object... arguments) { + if (isErrorEnabled()) { + handleArgArrayCall(Level.ERROR, null, format, arguments); + } + } + + public void error(String msg, Throwable t) { + if (isErrorEnabled()) { + handle_0ArgsCall(Level.ERROR, null, msg, t); + } + } + + public void error(Marker marker, String msg) { + if (isErrorEnabled(marker)) { + handle_0ArgsCall(Level.ERROR, marker, msg, null); + } + } + + public void error(Marker marker, String format, Object arg) { + if (isErrorEnabled(marker)) { + handle_1ArgsCall(Level.ERROR, marker, format, arg); + } + } + + public void error(Marker marker, String format, Object arg1, Object arg2) { + if (isErrorEnabled(marker)) { + handle2ArgsCall(Level.ERROR, marker, format, arg1, arg2); + } + } + + public void error(Marker marker, String format, Object... arguments) { + if (isErrorEnabled(marker)) { + handleArgArrayCall(Level.ERROR, marker, format, arguments); + } + } + + public void error(Marker marker, String msg, Throwable t) { + if (isErrorEnabled(marker)) { + handle_0ArgsCall(Level.ERROR, marker, msg, t); + } + } + + private void handle_0ArgsCall(Level level, Marker marker, String msg, Throwable t) { + handleNormalizedLoggingCall(level, marker, msg, null, t); + } + + private void handle_1ArgsCall(Level level, Marker marker, String msg, Object arg1) { + handleNormalizedLoggingCall(level, marker, msg, new Object[] { arg1 }, null); + } + + private void handle2ArgsCall(Level level, Marker marker, String msg, Object arg1, Object arg2) { + if (arg2 instanceof Throwable) { + handleNormalizedLoggingCall(level, marker, msg, new Object[] { arg1 }, (Throwable) arg2); + } else { + handleNormalizedLoggingCall(level, marker, msg, new Object[] { arg1, arg2 }, null); + } + } + + private void handleArgArrayCall(Level level, Marker marker, String msg, Object[] args) { + Throwable throwableCandidate = MessageFormatter.getThrowableCandidate(args); + if (throwableCandidate != null) { + Object[] trimmedCopy = MessageFormatter.trimmedCopy(args); + handleNormalizedLoggingCall(level, marker, msg, trimmedCopy, throwableCandidate); + } else { + handleNormalizedLoggingCall(level, marker, msg, args, null); + } + } + + abstract protected String getFullyQualifiedCallerName(); + + /** + * Given various arguments passed as parameters, perform actual logging. + * + * <p>This method assumes that the separation of the args array into actual + * objects and a throwable has been already operated. + * + * @param level the SLF4J level for this event + * @param marker The marker to be used for this event, may be null. + * @param messagePattern The message pattern which will be parsed and formatted + * @param arguments the array of arguments to be formatted, may be null + * @param throwable The exception whose stack trace should be logged, may be null + */ + abstract protected void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable); + +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/BasicMDCAdapter.java b/slf4j-api/src/main/java/org/slf4j/helpers/BasicMDCAdapter.java index 67ab9b5a..95cb904a 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/BasicMDCAdapter.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/BasicMDCAdapter.java @@ -27,45 +27,43 @@ package org.slf4j.helpers; import org.slf4j.spi.MDCAdapter; import java.util.*; -import java.util.Map; /** * Basic MDC implementation, which can be used with logging systems that lack * out-of-the-box MDC support. - * + * * This code was initially inspired by logback's LogbackMDCAdapter. However, * LogbackMDCAdapter has evolved and is now considerably more sophisticated. * * @author Ceki Gulcu * @author Maarten Bosteels + * @author Lukasz Cwik * * @since 1.5.0 */ public class BasicMDCAdapter implements MDCAdapter { - private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<Map<String, String>>(); + private final ThreadLocalMapOfStacks threadLocalMapOfDeques = new ThreadLocalMapOfStacks(); - static boolean isJDK14() { - try { - String javaVersion = System.getProperty("java.version"); - return javaVersion.startsWith("1.4"); - } catch (SecurityException se) { - // punt and assume JDK 1.5 or later - return false; + private final InheritableThreadLocal<Map<String, String>> inheritableThreadLocalMap = new InheritableThreadLocal<Map<String, String>>() { + @Override + protected Map<String, String> childValue(Map<String, String> parentValue) { + if (parentValue == null) { + return null; + } + return new HashMap<>(parentValue); } - } - - static boolean IS_JDK14 = isJDK14(); + }; /** * Put a context value (the <code>val</code> parameter) as identified with * the <code>key</code> parameter into the current thread's context map. * Note that contrary to log4j, the <code>val</code> parameter can be null. - * + * * <p> * If the current thread does not have a context map it is created as a side * effect of this call. - * + * * @throws IllegalArgumentException * in case the "key" parameter is null */ @@ -73,10 +71,10 @@ public class BasicMDCAdapter implements MDCAdapter { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } - Map<String, String> map = (Map<String, String>) inheritableThreadLocal.get(); + Map<String, String> map = inheritableThreadLocalMap.get(); if (map == null) { - map = Collections.<String, String> synchronizedMap(new HashMap<String, String>()); - inheritableThreadLocal.set(map); + map = new HashMap<>(); + inheritableThreadLocalMap.set(map); } map.put(key, val); } @@ -85,19 +83,19 @@ public class BasicMDCAdapter implements MDCAdapter { * Get the context identified by the <code>key</code> parameter. */ public String get(String key) { - Map<String, String> Map = (Map<String, String>) inheritableThreadLocal.get(); - if ((Map != null) && (key != null)) { - return (String) Map.get(key); + Map<String, String> map = inheritableThreadLocalMap.get(); + if ((map != null) && (key != null)) { + return map.get(key); } else { return null; } } /** - * Remove the the context identified by the <code>key</code> parameter. + * Remove the context identified by the <code>key</code> parameter. */ public void remove(String key) { - Map<String, String> map = (Map<String, String>) inheritableThreadLocal.get(); + Map<String, String> map = inheritableThreadLocalMap.get(); if (map != null) { map.remove(key); } @@ -107,27 +105,21 @@ public class BasicMDCAdapter implements MDCAdapter { * Clear all entries in the MDC. */ public void clear() { - Map<String, String> map = (Map<String, String>) inheritableThreadLocal.get(); + Map<String, String> map = inheritableThreadLocalMap.get(); if (map != null) { map.clear(); - // the InheritableThreadLocal.remove method was introduced in JDK 1.5 - // Thus, invoking clear() on previous JDK 1.4 will fail - if (isJDK14()) { - inheritableThreadLocal.set(null); - } else { - inheritableThreadLocal.remove(); - } + inheritableThreadLocalMap.remove(); } } /** * Returns the keys in the MDC as a {@link Set} of {@link String}s The * returned value can be null. - * + * * @return the keys in the MDC */ public Set<String> getKeys() { - Map<String, String> map = (Map<String, String>) inheritableThreadLocal.get(); + Map<String, String> map = inheritableThreadLocalMap.get(); if (map != null) { return map.keySet(); } else { @@ -136,26 +128,43 @@ public class BasicMDCAdapter implements MDCAdapter { } /** - * Return a copy of the current thread's context map. + * Return a copy of the current thread's context map. * Returned value may be null. - * + * */ public Map<String, String> getCopyOfContextMap() { - Map<String, String> oldMap = (Map<String, String>) inheritableThreadLocal.get(); + Map<String, String> oldMap = inheritableThreadLocalMap.get(); if (oldMap != null) { - Map<String, String> newMap = Collections.<String, String> synchronizedMap(new HashMap<String, String>()); - synchronized (oldMap) { - newMap.putAll(oldMap); - } - return newMap; + return new HashMap<>(oldMap); } else { return null; } } public void setContextMap(Map<String, String> contextMap) { - Map<String, String> map = Collections.<String, String> synchronizedMap(new HashMap<String, String>(contextMap)); - inheritableThreadLocal.set(map); + Map<String, String> copy = null; + if (contextMap != null) { + copy = new HashMap<>(contextMap); + } + inheritableThreadLocalMap.set(copy); + } + + @Override + public void pushByKey(String key, String value) { + threadLocalMapOfDeques.pushByKey(key, value); } + @Override + public String popByKey(String key) { + return threadLocalMapOfDeques.popByKey(key); + } + + @Override + public Deque<String> getCopyOfDequeByKey(String key) { + return threadLocalMapOfDeques.getCopyOfDequeByKey(key); + } + @Override + public void clearDequeByKey(String key) { + threadLocalMapOfDeques.clearDequeByKey(key); + } } diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java b/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java index b296d5d2..9e3a6aee 100755 --- a/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java @@ -24,10 +24,9 @@ */ package org.slf4j.helpers; -import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Vector; +import java.util.concurrent.CopyOnWriteArrayList; import org.slf4j.Marker; @@ -39,10 +38,9 @@ import org.slf4j.Marker; */ public class BasicMarker implements Marker { - private static final long serialVersionUID = 1803952589649545191L; - + private static final long serialVersionUID = -2849567615646933777L; private final String name; - private List<Marker> referenceList; + private final List<Marker> referenceList = new CopyOnWriteArrayList<>(); BasicMarker(String name) { if (name == null) { @@ -55,7 +53,7 @@ public class BasicMarker implements Marker { return name; } - public synchronized void add(Marker reference) { + public void add(Marker reference) { if (reference == null) { throw new IllegalArgumentException("A null value cannot be added to a Marker as reference."); } @@ -65,49 +63,28 @@ public class BasicMarker implements Marker { return; } else if (reference.contains(this)) { // avoid recursion - // a potential reference should not its future "parent" as a reference + // a potential reference should not hold its future "parent" as a reference return; } else { - // let's add the reference - if (referenceList == null) { - referenceList = new Vector<Marker>(); - } referenceList.add(reference); } - } - public synchronized boolean hasReferences() { - return ((referenceList != null) && (referenceList.size() > 0)); + public boolean hasReferences() { + return (referenceList.size() > 0); } + @Deprecated public boolean hasChildren() { return hasReferences(); } - public synchronized Iterator<Marker> iterator() { - if (referenceList != null) { - return referenceList.iterator(); - } else { - List<Marker> emptyList = Collections.emptyList(); - return emptyList.iterator(); - } + public Iterator<Marker> iterator() { + return referenceList.iterator(); } - public synchronized boolean remove(Marker referenceToRemove) { - if (referenceList == null) { - return false; - } - - int size = referenceList.size(); - for (int i = 0; i < size; i++) { - Marker m = (Marker) referenceList.get(i); - if (referenceToRemove.equals(m)) { - referenceList.remove(i); - return true; - } - } - return false; + public boolean remove(Marker referenceToRemove) { + return referenceList.remove(referenceToRemove); } public boolean contains(Marker other) { @@ -120,8 +97,7 @@ public class BasicMarker implements Marker { } if (hasReferences()) { - for (int i = 0; i < referenceList.size(); i++) { - Marker ref = (Marker) referenceList.get(i); + for (Marker ref : referenceList) { if (ref.contains(other)) { return true; } @@ -143,8 +119,7 @@ public class BasicMarker implements Marker { } if (hasReferences()) { - for (int i = 0; i < referenceList.size(); i++) { - Marker ref = (Marker) referenceList.get(i); + for (Marker ref : referenceList) { if (ref.contains(name)) { return true; } @@ -153,9 +128,9 @@ public class BasicMarker implements Marker { return false; } - private static String OPEN = "[ "; - private static String CLOSE = " ]"; - private static String SEP = ", "; + private static final String OPEN = "[ "; + private static final String CLOSE = " ]"; + private static final String SEP = ", "; public boolean equals(Object obj) { if (this == obj) @@ -182,7 +157,7 @@ public class BasicMarker implements Marker { StringBuilder sb = new StringBuilder(this.getName()); sb.append(' ').append(OPEN); while (it.hasNext()) { - reference = (Marker) it.next(); + reference = it.next(); sb.append(reference.getName()); if (it.hasNext()) { sb.append(SEP); diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarkerFactory.java b/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarkerFactory.java index 5139bb66..06b0ec58 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarkerFactory.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarkerFactory.java @@ -41,7 +41,7 @@ import org.slf4j.Marker; */ public class BasicMarkerFactory implements IMarkerFactory { - private final ConcurrentMap<String, Marker> markerMap = new ConcurrentHashMap<String, Marker>(); + private final ConcurrentMap<String, Marker> markerMap = new ConcurrentHashMap<>(); /** * Regular users should <em>not</em> create diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/CheckReturnValue.java b/slf4j-api/src/main/java/org/slf4j/helpers/CheckReturnValue.java new file mode 100644 index 00000000..e1c9803a --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/CheckReturnValue.java @@ -0,0 +1,30 @@ +package org.slf4j.helpers; + + +import org.slf4j.Marker; + +import java.lang.annotation.*; + +/** + * <p>Used to annotate methods in the {@link org.slf4j.spi.LoggingEventBuilder} interface + * which return an instance of LoggingEventBuilder (usually as <code>this</code>). Such + * methods should be followed by one of the terminating <code>log()</code> methods returning + * <code>void</code>.</p> + * <p></p> + * <p>IntelliJ IDEA supports a check for annotations named as <code>CheckReturnValue</code> + * regardless of the containing package. For more information on this feature in IntelliJ IDEA, + * select File → Setting → Editor → Inspections and then Java → Probable Bugs → + * Result of method call ignored. </p> + * <p></p> + * + * <p>As for Eclipse, this feature has been requested in + * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572496">bug 572496</a></p> + * + * @author Ceki Gülcü + * @since 2.0.0-beta1 + */ +@Documented +@Target( { ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckReturnValue { +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/FormattingTuple.java b/slf4j-api/src/main/java/org/slf4j/helpers/FormattingTuple.java index 8d49ba45..a416e1d4 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/FormattingTuple.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/FormattingTuple.java @@ -33,9 +33,9 @@ public class FormattingTuple { static public FormattingTuple NULL = new FormattingTuple(null); - private String message; - private Throwable throwable; - private Object[] argArray; + private final String message; + private final Throwable throwable; + private final Object[] argArray; public FormattingTuple(String message) { this(message, null, null); @@ -44,21 +44,7 @@ public class FormattingTuple { public FormattingTuple(String message, Object[] argArray, Throwable throwable) { this.message = message; this.throwable = throwable; - if (throwable == null) { - this.argArray = argArray; - } else { - this.argArray = trimmedCopy(argArray); - } - } - - static Object[] trimmedCopy(Object[] argArray) { - if (argArray == null || argArray.length == 0) { - throw new IllegalStateException("non-sensical empty or null argument array"); - } - final int trimemdLen = argArray.length - 1; - Object[] trimmed = new Object[trimemdLen]; - System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); - return trimmed; + this.argArray = argArray; } public String getMessage() { diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/LegacyAbstractLogger.java b/slf4j-api/src/main/java/org/slf4j/helpers/LegacyAbstractLogger.java new file mode 100644 index 00000000..dcc90009 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/LegacyAbstractLogger.java @@ -0,0 +1,39 @@ +package org.slf4j.helpers; + +import org.slf4j.Marker; + +/** + * Provides minimal default implementations for {@link #isTraceEnabled(Marker)}, {@link #isDebugEnabled(Marker)} and other similar methods. + * + * @since 2.0 + */ +abstract public class LegacyAbstractLogger extends AbstractLogger { + + private static final long serialVersionUID = -7041884104854048950L; + + @Override + public boolean isTraceEnabled(Marker marker) { + return isTraceEnabled(); + } + + @Override + public boolean isDebugEnabled(Marker marker) { + return isDebugEnabled(); + } + + @Override + public boolean isInfoEnabled(Marker marker) { + return isInfoEnabled(); + } + + @Override + public boolean isWarnEnabled(Marker marker) { + return isWarnEnabled(); + } + + @Override + public boolean isErrorEnabled(Marker marker) { + return isErrorEnabled(); + } + +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/MarkerIgnoringBase.java b/slf4j-api/src/main/java/org/slf4j/helpers/MarkerIgnoringBase.java index dd765384..715cec75 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/MarkerIgnoringBase.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/MarkerIgnoringBase.java @@ -34,6 +34,7 @@ import org.slf4j.Marker; * any marker data passed as argument. * * @author Ceki Gulcu + * @deprecated */ public abstract class MarkerIgnoringBase extends NamedLoggerBase implements Logger { diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/MessageFormatter.java b/slf4j-api/src/main/java/org/slf4j/helpers/MessageFormatter.java index cbecbfc6..47f95daa 100755 --- a/slf4j-api/src/main/java/org/slf4j/helpers/MessageFormatter.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/MessageFormatter.java @@ -33,14 +33,14 @@ import java.util.Map; /** * Formats messages according to very simple substitution rules. Substitutions * can be made 1, 2 or more arguments. - * + * * <p> * For example, - * + * * <pre> * MessageFormatter.format("Hi {}.", "there") * </pre> - * + * * will return the string "Hi there.". * <p> * The {} pair is called the <em>formatting anchor</em>. It serves to designate @@ -50,48 +50,48 @@ import java.util.Map; * In case your message contains the '{' or the '}' character, you do not have * to do anything special unless the '}' character immediately follows '{'. For * example, - * + * * <pre> * MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2"); * </pre> - * + * * will return the string "Set {1,2,3} is not equal to 1,2.". - * + * * <p> * If for whatever reason you need to place the string "{}" in the message * without its <em>formatting anchor</em> meaning, then you need to escape the * '{' character with '\', that is the backslash character. Only the '{' * character should be escaped. There is no need to escape the '}' character. * For example, - * + * * <pre> * MessageFormatter.format("Set \\{} is not equal to {}.", "1,2"); * </pre> - * + * * will return the string "Set {} is not equal to 1,2.". - * + * * <p> * The escaping behavior just described can be overridden by escaping the escape * character '\'. Calling - * + * * <pre> * MessageFormatter.format("File name is C:\\\\{}.", "file.zip"); * </pre> - * + * * will return the string "File name is C:\file.zip". - * + * * <p> - * The formatting conventions are different than those of {@link MessageFormat} + * The formatting conventions are different from those of {@link MessageFormat} * which ships with the Java platform. This is justified by the fact that * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}. * This local performance difference is both measurable and significant in the * larger context of the complete logging processing chain. - * + * * <p> * See also {@link #format(String, Object)}, * {@link #format(String, Object, Object)} and * {@link #arrayFormat(String, Object[])} methods for more details. - * + * * @author Ceki Gülcü * @author Joern Huxhorn */ @@ -106,17 +106,17 @@ final public class MessageFormatter { * parameter. * <p> * For example, - * + * * <pre> * MessageFormatter.format("Hi {}.", "there"); * </pre> - * + * * will return the string "Hi there.". * <p> - * + * * @param messagePattern * The message pattern which will be parsed and formatted - * @param argument + * @param arg * The argument to be substituted in place of the formatting anchor * @return The formatted message */ @@ -125,18 +125,18 @@ final public class MessageFormatter { } /** - * + * * Performs a two argument substitution for the 'messagePattern' passed as * parameter. * <p> * For example, - * + * * <pre> * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob"); * </pre> - * + * * will return the string "Hi Alice. My name is Bob.". - * + * * @param messagePattern * The message pattern which will be parsed and formatted * @param arg1 @@ -151,36 +151,34 @@ final public class MessageFormatter { return arrayFormat(messagePattern, new Object[] { arg1, arg2 }); } - static final Throwable getThrowableCandidate(Object[] argArray) { - if (argArray == null || argArray.length == 0) { - return null; - } - - final Object lastEntry = argArray[argArray.length - 1]; - if (lastEntry instanceof Throwable) { - return (Throwable) lastEntry; + final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { + Throwable throwableCandidate = MessageFormatter.getThrowableCandidate(argArray); + Object[] args = argArray; + if (throwableCandidate != null) { + args = MessageFormatter.trimmedCopy(argArray); } - return null; + return arrayFormat(messagePattern, args, throwableCandidate); } /** - * Same principle as the {@link #format(String, Object)} and - * {@link #format(String, Object, Object)} methods except that any number of - * arguments can be passed in an array. + * Assumes that argArray only contains arguments with no throwable as last element. * * @param messagePattern - * The message pattern which will be parsed and formatted * @param argArray - * An array of arguments to be substituted in place of formatting - * anchors - * @return The formatted message */ - final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { + final public static String basicArrayFormat(final String messagePattern, final Object[] argArray) { + FormattingTuple ft = arrayFormat(messagePattern, argArray, null); + return ft.getMessage(); + } - Throwable throwableCandidate = getThrowableCandidate(argArray); + public static String basicArrayFormat(NormalizedParameters np) { + return basicArrayFormat(np.getMessage(), np.getArguments()); + } + + final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { if (messagePattern == null) { - return new FormattingTuple(null, argArray, throwableCandidate); + return new FormattingTuple(null, argArray, throwable); } if (argArray == null) { @@ -200,42 +198,38 @@ final public class MessageFormatter { if (j == -1) { // no more variables if (i == 0) { // this is a simple string - return new FormattingTuple(messagePattern, argArray, throwableCandidate); + return new FormattingTuple(messagePattern, argArray, throwable); } else { // add the tail string which contains no variables and return // the result. - sbuf.append(messagePattern.substring(i, messagePattern.length())); - return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); + sbuf.append(messagePattern, i, messagePattern.length()); + return new FormattingTuple(sbuf.toString(), argArray, throwable); } } else { if (isEscapedDelimeter(messagePattern, j)) { if (!isDoubleEscaped(messagePattern, j)) { L--; // DELIM_START was escaped, thus should not be incremented - sbuf.append(messagePattern.substring(i, j - 1)); + sbuf.append(messagePattern, i, j - 1); sbuf.append(DELIM_START); i = j + 1; } else { // The escape character preceding the delimiter start is // itself escaped: "abc x:\\{}" // we have to consume one backward slash - sbuf.append(messagePattern.substring(i, j - 1)); - deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>()); + sbuf.append(messagePattern, i, j - 1); + deeplyAppendParameter(sbuf, argArray[L], new HashMap<>()); i = j + 2; } } else { // normal case - sbuf.append(messagePattern.substring(i, j)); - deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>()); + sbuf.append(messagePattern, i, j); + deeplyAppendParameter(sbuf, argArray[L], new HashMap<>()); i = j + 2; } } } // append the characters following the last {} pair. - sbuf.append(messagePattern.substring(i, messagePattern.length())); - if (L < argArray.length - 1) { - return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); - } else { - return new FormattingTuple(sbuf.toString(), argArray, null); - } + sbuf.append(messagePattern, i, messagePattern.length()); + return new FormattingTuple(sbuf.toString(), argArray, throwable); } final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) { @@ -297,8 +291,7 @@ final public class MessageFormatter { String oAsString = o.toString(); sbuf.append(oAsString); } catch (Throwable t) { - System.err.println("SLF4J: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]"); - t.printStackTrace(); + Reporter.error("Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t); sbuf.append("[FAILED toString()]"); } @@ -409,4 +402,29 @@ final public class MessageFormatter { } sbuf.append(']'); } + + /** + * Helper method to determine if an {@link Object} array contains a {@link Throwable} as last element + * + * @param argArray + * The arguments off which we want to know if it contains a {@link Throwable} as last element + * @return if the last {@link Object} in argArray is a {@link Throwable} this method will return it, + * otherwise it returns null + */ + public static Throwable getThrowableCandidate(final Object[] argArray) { + return NormalizedParameters.getThrowableCandidate(argArray); + } + + /** + * Helper method to get all but the last element of an array + * + * @param argArray + * The arguments from which we want to remove the last element + * + * @return a copy of the array without the last element + */ + public static Object[] trimmedCopy(final Object[] argArray) { + return NormalizedParameters.trimmedCopy(argArray); + } + } diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/NOPLogger.java b/slf4j-api/src/main/java/org/slf4j/helpers/NOPLogger.java index 5e0909d8..4b9a3597 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/NOPLogger.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/NOPLogger.java @@ -25,14 +25,14 @@ package org.slf4j.helpers; import org.slf4j.Logger; -import org.slf4j.helpers.MarkerIgnoringBase; +import org.slf4j.Marker; /** * A direct NOP (no operation) implementation of {@link Logger}. * * @author Ceki Gülcü */ -public class NOPLogger extends MarkerIgnoringBase { +public class NOPLogger extends NamedLoggerBase implements Logger { private static final long serialVersionUID = -517220405410904473L; @@ -42,8 +42,9 @@ public class NOPLogger extends MarkerIgnoringBase { public static final NOPLogger NOP_LOGGER = new NOPLogger(); /** - * There is no point in creating multiple instances of NOPLOgger, - * except by derived classes, hence the protected access for the constructor. + * There is no point in creating multiple instances of NOPLogger. + * + * The present constructor should be "private" but we are leaving it as "protected" for compatibility. */ protected NOPLogger() { } @@ -51,6 +52,7 @@ public class NOPLogger extends MarkerIgnoringBase { /** * Always returns the string value "NOP". */ + @Override public String getName() { return "NOP"; } @@ -59,31 +61,37 @@ public class NOPLogger extends MarkerIgnoringBase { * Always returns false. * @return always false */ + @Override final public boolean isTraceEnabled() { return false; } /** A NOP implementation. */ + @Override final public void trace(String msg) { // NOP } /** A NOP implementation. */ + @Override final public void trace(String format, Object arg) { // NOP } /** A NOP implementation. */ + @Override public final void trace(String format, Object arg1, Object arg2) { // NOP } /** A NOP implementation. */ + @Override public final void trace(String format, Object... argArray) { // NOP } /** A NOP implementation. */ + @Override final public void trace(String msg, Throwable t) { // NOP } @@ -107,12 +115,12 @@ public class NOPLogger extends MarkerIgnoringBase { } /** A NOP implementation. */ - public final void debug(String format, Object arg1, Object arg2) { + final public void debug(String format, Object arg1, Object arg2) { // NOP } /** A NOP implementation. */ - public final void debug(String format, Object... argArray) { + final public void debug(String format, Object... argArray) { // NOP } @@ -146,7 +154,7 @@ public class NOPLogger extends MarkerIgnoringBase { } /** A NOP implementation. */ - public final void info(String format, Object... argArray) { + final public void info(String format, Object... argArray) { // NOP } @@ -179,7 +187,7 @@ public class NOPLogger extends MarkerIgnoringBase { } /** A NOP implementation. */ - public final void warn(String format, Object... argArray) { + final public void warn(String format, Object... argArray) { // NOP } @@ -209,7 +217,7 @@ public class NOPLogger extends MarkerIgnoringBase { } /** A NOP implementation. */ - public final void error(String format, Object... argArray) { + final public void error(String format, Object... argArray) { // NOP } @@ -217,4 +225,203 @@ public class NOPLogger extends MarkerIgnoringBase { final public void error(String msg, Throwable t) { // NOP } + + // ============================================================ + // Added NOP methods since MarkerIgnoringBase is now deprecated + // ============================================================ + /** + * Always returns false. + * @return always false + */ + final public boolean isTraceEnabled(Marker marker) { + // NOP + return false; + } + + /** A NOP implementation. */ + @Override + final public void trace(Marker marker, String msg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void trace(Marker marker, String format, Object arg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void trace(Marker marker, String format, Object arg1, Object arg2) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void trace(Marker marker, String format, Object... argArray) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void trace(Marker marker, String msg, Throwable t) { + // NOP + } + + /** + * Always returns false. + * @return always false + */ + final public boolean isDebugEnabled(Marker marker) { + return false; + } + + /** A NOP implementation. */ + @Override + final public void debug(Marker marker, String msg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void debug(Marker marker, String format, Object arg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void debug(Marker marker, String format, Object arg1, Object arg2) { + // NOP + } + + @Override + final public void debug(Marker marker, String format, Object... arguments) { + // NOP + } + + @Override + final public void debug(Marker marker, String msg, Throwable t) { + // NOP + } + + /** + * Always returns false. + * @return always false + */ + @Override + public boolean isInfoEnabled(Marker marker) { + return false; + } + + /** A NOP implementation. */ + @Override + final public void info(Marker marker, String msg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void info(Marker marker, String format, Object arg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void info(Marker marker, String format, Object arg1, Object arg2) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void info(Marker marker, String format, Object... arguments) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void info(Marker marker, String msg, Throwable t) { + // NOP + } + + /** + * Always returns false. + * @return always false + */ + @Override + final public boolean isWarnEnabled(Marker marker) { + return false; + } + + /** A NOP implementation. */ + @Override + final public void warn(Marker marker, String msg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void warn(Marker marker, String format, Object arg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void warn(Marker marker, String format, Object arg1, Object arg2) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void warn(Marker marker, String format, Object... arguments) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void warn(Marker marker, String msg, Throwable t) { + // NOP + } + + /** + * Always returns false. + * @return always false + */ + @Override + final public boolean isErrorEnabled(Marker marker) { + return false; + } + + /** A NOP implementation. */ + @Override + final public void error(Marker marker, String msg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void error(Marker marker, String format, Object arg) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void error(Marker marker, String format, Object arg1, Object arg2) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void error(Marker marker, String format, Object... arguments) { + // NOP + } + + /** A NOP implementation. */ + @Override + final public void error(Marker marker, String msg, Throwable t) { + // NOP + } + // =================================================================== + // End of added NOP methods since MarkerIgnoringBase is now deprecated + // =================================================================== + } diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/NOPLoggerFactory.java b/slf4j-api/src/main/java/org/slf4j/helpers/NOPLoggerFactory.java index b8a55dc9..7cc83914 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/NOPLoggerFactory.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/NOPLoggerFactory.java @@ -26,10 +26,9 @@ package org.slf4j.helpers; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; -import org.slf4j.helpers.NOPLogger; /** - * NOPLoggerFactory is an trivial implementation of {@link + * NOPLoggerFactory is a trivial implementation of {@link * ILoggerFactory} which always returns the unique instance of * NOPLogger. * diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/NOPMDCAdapter.java b/slf4j-api/src/main/java/org/slf4j/helpers/NOPMDCAdapter.java index 22f3ea45..7c34fca0 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/NOPMDCAdapter.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/NOPMDCAdapter.java @@ -24,6 +24,7 @@ */ package org.slf4j.helpers; +import java.util.Deque; import java.util.Map; import org.slf4j.spi.MDCAdapter; @@ -60,4 +61,21 @@ public class NOPMDCAdapter implements MDCAdapter { // NOP } + @Override + public void pushByKey(String key, String value) { + } + + @Override + public String popByKey(String key) { + return null; + } + + @Override + public Deque<String> getCopyOfDequeByKey(String key) { + return null; + } + + public void clearDequeByKey(String key) { + } + } diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/NOP_FallbackServiceProvider.java b/slf4j-api/src/main/java/org/slf4j/helpers/NOP_FallbackServiceProvider.java new file mode 100755 index 00000000..87d287a6 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/NOP_FallbackServiceProvider.java @@ -0,0 +1,47 @@ +package org.slf4j.helpers; + +import org.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; + +public class NOP_FallbackServiceProvider implements SLF4JServiceProvider { + + /** + * Declare the version of the SLF4J API this implementation is compiled + * against. The value of this field is modified with each major release. + */ + // to avoid constant folding by the compiler, this field must *not* be final + public static String REQUESTED_API_VERSION = "2.0.99"; // !final + + private final ILoggerFactory loggerFactory = new NOPLoggerFactory(); + private final IMarkerFactory markerFactory = new BasicMarkerFactory(); + private final MDCAdapter mdcAdapter = new NOPMDCAdapter(); + + + @Override + public ILoggerFactory getLoggerFactory() { + return loggerFactory; + } + + @Override + public IMarkerFactory getMarkerFactory() { + return markerFactory; + } + + + @Override + public MDCAdapter getMDCAdapter() { + return mdcAdapter; + } + + @Override + public String getRequestedApiVersion() { + return REQUESTED_API_VERSION; + } + + @Override + public void initialize() { + // already initialized + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/NamedLoggerBase.java b/slf4j-api/src/main/java/org/slf4j/helpers/NamedLoggerBase.java index 184c3942..97b31837 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/NamedLoggerBase.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/NamedLoggerBase.java @@ -32,9 +32,10 @@ import org.slf4j.LoggerFactory; /** * Serves as base class for named logger implementation. More significantly, this - * class establishes deserialization behavior. See @see #readResolve. + * class establishes deserialization behavior. * * @author Ceki Gulcu + * @see #readResolve * @since 1.5.3 */ abstract class NamedLoggerBase implements Logger, Serializable { @@ -54,7 +55,7 @@ abstract class NamedLoggerBase implements Logger, Serializable { * * <p> * This approach will work well if the desired ILoggerFactory is the one - * references by LoggerFactory. However, if the user manages its logger hierarchy + * referenced by LoggerFactory. However, if the user manages its logger hierarchy * through a different (non-static) mechanism, e.g. dependency injection, then * this approach would be mostly counterproductive. * diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/NormalizedParameters.java b/slf4j-api/src/main/java/org/slf4j/helpers/NormalizedParameters.java new file mode 100644 index 00000000..ec278f46 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/NormalizedParameters.java @@ -0,0 +1,116 @@ +package org.slf4j.helpers; + +import org.slf4j.event.LoggingEvent; + +/** + * Holds normalized call parameters. + * + * Includes utility methods such as {@link #normalize(String, Object[], Throwable)} to help the normalization of parameters. + * + * @author ceki + * @since 2.0 + */ +public class NormalizedParameters { + + final String message; + final Object[] arguments; + final Throwable throwable; + + public NormalizedParameters(String message, Object[] arguments, Throwable throwable) { + this.message = message; + this.arguments = arguments; + this.throwable = throwable; + } + + public NormalizedParameters(String message, Object[] arguments) { + this(message, arguments, null); + } + + public String getMessage() { + return message; + } + + public Object[] getArguments() { + return arguments; + } + + public Throwable getThrowable() { + return throwable; + } + + /** + * Helper method to determine if an {@link Object} array contains a + * {@link Throwable} as last element + * + * @param argArray The arguments off which we want to know if it contains a + * {@link Throwable} as last element + * @return if the last {@link Object} in argArray is a {@link Throwable} this + * method will return it, otherwise it returns null + */ + public static Throwable getThrowableCandidate(final Object[] argArray) { + if (argArray == null || argArray.length == 0) { + return null; + } + + final Object lastEntry = argArray[argArray.length - 1]; + if (lastEntry instanceof Throwable) { + return (Throwable) lastEntry; + } + + return null; + } + + /** + * Helper method to get all but the last element of an array + * + * @param argArray The arguments from which we want to remove the last element + * + * @return a copy of the array without the last element + */ + public static Object[] trimmedCopy(final Object[] argArray) { + if (argArray == null || argArray.length == 0) { + throw new IllegalStateException("non-sensical empty or null argument array"); + } + + final int trimmedLen = argArray.length - 1; + + Object[] trimmed = new Object[trimmedLen]; + + if (trimmedLen > 0) { + System.arraycopy(argArray, 0, trimmed, 0, trimmedLen); + } + + return trimmed; + } + + /** + * This method serves to normalize logging call invocation parameters. + * + * More specifically, if a throwable argument is not supplied directly, it + * attempts to extract it from the argument array. + */ + public static NormalizedParameters normalize(String msg, Object[] arguments, Throwable t) { + + if (t != null) { + return new NormalizedParameters(msg, arguments, t); + } + + if (arguments == null || arguments.length == 0) { + return new NormalizedParameters(msg, arguments, t); + } + + Throwable throwableCandidate = NormalizedParameters.getThrowableCandidate(arguments); + if (throwableCandidate != null) { + Object[] trimmedArguments = MessageFormatter.trimmedCopy(arguments); + return new NormalizedParameters(msg, trimmedArguments, throwableCandidate); + } else { + return new NormalizedParameters(msg, arguments); + } + + } + + public static NormalizedParameters normalize(LoggingEvent event) { + return normalize(event.getMessage(), event.getArgumentArray(), event.getThrowable()); + } + +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/Reporter.java b/slf4j-api/src/main/java/org/slf4j/helpers/Reporter.java new file mode 100644 index 00000000..2f269969 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/Reporter.java @@ -0,0 +1,181 @@ +package org.slf4j.helpers; + +import java.io.PrintStream; + +/** + * An internally used class for reporting internal messages generated by SLF4J itself during initialization. + * + * <p> + * Internal reporting is performed by calling the {@link #info(String)}, {@link #warn(String)} (String)} + * {@link #error(String)} (String)} and {@link #error(String, Throwable)} methods. + * </p> + * <p>See {@link #SLF4J_INTERNAL_VERBOSITY_KEY} and {@link #SLF4J_INTERNAL_REPORT_STREAM_KEY} for + * configuration options.</p> + * <p> + * <p> + * Note that this system is independent of the logging back-end in use. + * + * @since 2.0.10 + */ +public class Reporter { + + /** + * this class is used internally by Reporter + */ + private enum Level { + INFO(1), WARN(2), ERROR(3); + + int levelInt; + + private Level(int levelInt) { + this.levelInt = levelInt; + } + + private int getLevelInt() { + return levelInt; + } + } + + private enum TargetChoice { + Stderr, Stdout; + } + + static final String SLF4J_INFO_PREFIX = "SLF4J(I): "; + static final String SLF4J_WARN_PREFIX = "SLF4J(W): "; + static final String SLF4J_ERROR_PREFIX = "SLF4J(E): "; + + + /** + * This system property controls the target for internal reports output by SLF4J. + * Recognized values for this key are "System.out", "stdout", "sysout", "System.err", + * "stderr" and "syserr". + * + * <p>By default, output is directed to "stderr".</p> + */ + public static final String SLF4J_INTERNAL_REPORT_STREAM_KEY = "slf4j.internal.report.stream"; + static private final String[] SYSOUT_KEYS = {"System.out", "stdout", "sysout"}; + + /** + * This system property controls the internal level of chattiness + * of SLF4J. Recognized settings are "INFO", "WARN" and "ERROR". The default value is "INFO". + */ + public static final String SLF4J_INTERNAL_VERBOSITY_KEY = "slf4j.internal.verbosity"; + + + static private final TargetChoice TARGET_CHOICE = initTargetChoice(); + + static private final Level INTERNAL_VERBOSITY = initVerbosity(); + + static private TargetChoice initTargetChoice() { + String reportStreamStr = System.getProperty(SLF4J_INTERNAL_REPORT_STREAM_KEY); + + if(reportStreamStr == null || reportStreamStr.isEmpty()) { + return TargetChoice.Stderr; + } + + for(String s : SYSOUT_KEYS) { + if(s.equalsIgnoreCase(reportStreamStr)) + return TargetChoice.Stdout; + } + return TargetChoice.Stderr; + } + + + static private Level initVerbosity() { + String verbosityStr = System.getProperty(SLF4J_INTERNAL_VERBOSITY_KEY); + + if(verbosityStr == null || verbosityStr.isEmpty()) { + return Level.INFO; + } + + if(verbosityStr.equalsIgnoreCase("ERROR")) { + return Level.ERROR; + } + + + if(verbosityStr.equalsIgnoreCase("WARN")) { + return Level.WARN; + } + + return Level.INFO; + } + + static boolean isEnabledFor(Level level) { + return (level.levelInt >= INTERNAL_VERBOSITY.levelInt); + } + + static private PrintStream getTarget() { + switch(TARGET_CHOICE) { + case Stdout: + return System.out; + case Stderr: + default: + return System.err; + } + } + + /** + * Report an internal message of level INFO. Message text is prefixed with the string "SLF4J(I)", with + * (I) standing as a shorthand for INFO. + * + * <p>Messages of level INFO are be enabled when the {@link #SLF4J_INTERNAL_VERBOSITY_KEY} system property is + * set to "INFO" and disabled when set to "WARN" or "ERROR". By default, {@link #SLF4J_INTERNAL_VERBOSITY_KEY} is + * set to "INFO".</p> + * + * @param msg the message text + */ + public static void info(String msg) { + if(isEnabledFor(Level.INFO)) { + getTarget().println(SLF4J_INFO_PREFIX + msg); + } + } + + + /** + * Report an internal message of level "WARN". Message text is prefixed with the string "SLF4J(W)", with + * (W) standing as a shorthand for WARN. + * + * <p>Messages of level WARN are be enabled when the {@link #SLF4J_INTERNAL_VERBOSITY_KEY} system property is + * set to "INFO" or "WARN" and disabled when set to "ERROR". By default, {@link #SLF4J_INTERNAL_VERBOSITY_KEY} is + * set to "INFO".</p> + * + * @param msg the message text + */ + static final public void warn(String msg) { + if(isEnabledFor(Level.WARN)) { + getTarget().println(SLF4J_WARN_PREFIX + msg); + } + } + + + /** + * Report an internal message of level "ERROR accompanied by a {@link Throwable}. + * Message text is prefixed with the string "SLF4J(E)", with (E) standing as a shorthand for ERROR. + * + * <p>Messages of level ERROR are always enabled. + * + * @param msg the message text + * @param t a Throwable + */ + static final public void error(String msg, Throwable t) { + // error cannot be disabled + getTarget().println(SLF4J_ERROR_PREFIX + msg); + getTarget().println(SLF4J_ERROR_PREFIX + "Reported exception:"); + t.printStackTrace(getTarget()); + } + + + /** + * Report an internal message of level "ERROR". Message text is prefixed with the string "SLF4J(E)", with + * (E) standing as a shorthand for ERROR. + * + * <p>Messages of level ERROR are always enabled. + * + * @param msg the message text + */ + + static final public void error(String msg) { + // error cannot be disabled + getTarget().println(SLF4J_ERROR_PREFIX + msg); + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLogger.java b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLogger.java index 1c37977e..b9e160d7 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLogger.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLogger.java @@ -24,196 +24,286 @@ */ package org.slf4j.helpers; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Queue; + import org.slf4j.Logger; import org.slf4j.Marker; +import org.slf4j.event.EventRecordingLogger; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; +import org.slf4j.event.SubstituteLoggingEvent; +import org.slf4j.spi.LoggingEventBuilder; /** * A logger implementation which logs via a delegate logger. By default, the delegate is a - * {@link NOPLogger}. However, a different delegate can be set at anytime. - * <p/> - * See also the <a href="http://www.slf4j.org/codes.html#substituteLogger">relevant + * {@link NOPLogger}. However, a different delegate can be set at any time. + * + * <p>See also the <a href="http://www.slf4j.org/codes.html#substituteLogger">relevant * error code</a> documentation. * * @author Chetan Mehrotra + * @author Ceki Gulcu */ public class SubstituteLogger implements Logger { private final String name; - private volatile Logger _delegate; + private Boolean delegateEventAware; + private Method logMethodCache; + private EventRecordingLogger eventRecordingLogger; + private final Queue<SubstituteLoggingEvent> eventQueue; + + public final boolean createdPostInitialization; - public SubstituteLogger(String name) { + public SubstituteLogger(String name, Queue<SubstituteLoggingEvent> eventQueue, boolean createdPostInitialization) { this.name = name; + this.eventQueue = eventQueue; + this.createdPostInitialization = createdPostInitialization; } + @Override public String getName() { return name; } + + @Override + public LoggingEventBuilder makeLoggingEventBuilder(Level level) { + return delegate().makeLoggingEventBuilder(level); + } + @Override + public LoggingEventBuilder atLevel(Level level) { + return delegate().atLevel(level); + } + + @Override + public boolean isEnabledForLevel(Level level) { + return delegate().isEnabledForLevel(level); + } + + @Override public boolean isTraceEnabled() { return delegate().isTraceEnabled(); } - + + @Override public void trace(String msg) { delegate().trace(msg); } - + + @Override public void trace(String format, Object arg) { delegate().trace(format, arg); } - + + @Override public void trace(String format, Object arg1, Object arg2) { delegate().trace(format, arg1, arg2); } - + + @Override public void trace(String format, Object... arguments) { delegate().trace(format, arguments); - } - + } + + @Override public void trace(String msg, Throwable t) { delegate().trace(msg, t); } - + + @Override public boolean isTraceEnabled(Marker marker) { return delegate().isTraceEnabled(marker); } - + + @Override public void trace(Marker marker, String msg) { delegate().trace(marker, msg); } - + + @Override public void trace(Marker marker, String format, Object arg) { delegate().trace(marker, format, arg); } - + + @Override public void trace(Marker marker, String format, Object arg1, Object arg2) { delegate().trace(marker, format, arg1, arg2); } - + @Override public void trace(Marker marker, String format, Object... arguments) { delegate().trace(marker, format, arguments); } - + @Override public void trace(Marker marker, String msg, Throwable t) { delegate().trace(marker, msg, t); } - + + @Override + public LoggingEventBuilder atTrace() { + return delegate().atTrace(); + } + + @Override public boolean isDebugEnabled() { return delegate().isDebugEnabled(); } - + + @Override public void debug(String msg) { delegate().debug(msg); } - + + @Override public void debug(String format, Object arg) { delegate().debug(format, arg); } - + + @Override public void debug(String format, Object arg1, Object arg2) { delegate().debug(format, arg1, arg2); } - + + @Override public void debug(String format, Object... arguments) { delegate().debug(format, arguments); } - + + @Override public void debug(String msg, Throwable t) { delegate().debug(msg, t); } - + + @Override public boolean isDebugEnabled(Marker marker) { return delegate().isDebugEnabled(marker); } - + + @Override public void debug(Marker marker, String msg) { delegate().debug(marker, msg); } - + + @Override public void debug(Marker marker, String format, Object arg) { delegate().debug(marker, format, arg); } - + + @Override public void debug(Marker marker, String format, Object arg1, Object arg2) { delegate().debug(marker, format, arg1, arg2); } - + + @Override public void debug(Marker marker, String format, Object... arguments) { delegate().debug(marker, format, arguments); } - + + @Override public void debug(Marker marker, String msg, Throwable t) { delegate().debug(marker, msg, t); } - + + @Override + public LoggingEventBuilder atDebug() { + return delegate().atDebug(); + } + + @Override public boolean isInfoEnabled() { return delegate().isInfoEnabled(); } + + @Override public void info(String msg) { delegate().info(msg); } - + + @Override public void info(String format, Object arg) { delegate().info(format, arg); } - + + @Override public void info(String format, Object arg1, Object arg2) { delegate().info(format, arg1, arg2); } - + + @Override public void info(String format, Object... arguments) { delegate().info(format, arguments); } - + + @Override public void info(String msg, Throwable t) { delegate().info(msg, t); } - + + @Override public boolean isInfoEnabled(Marker marker) { return delegate().isInfoEnabled(marker); } - + + @Override public void info(Marker marker, String msg) { delegate().info(marker, msg); } - + + @Override public void info(Marker marker, String format, Object arg) { delegate().info(marker, format, arg); } - + + @Override public void info(Marker marker, String format, Object arg1, Object arg2) { delegate().info(marker, format, arg1, arg2); } - + + @Override public void info(Marker marker, String format, Object... arguments) { delegate().info(marker, format, arguments); } - + + @Override public void info(Marker marker, String msg, Throwable t) { delegate().info(marker, msg, t); } + + @Override + public LoggingEventBuilder atInfo() { + return delegate().atInfo(); + } + + @Override public boolean isWarnEnabled() { return delegate().isWarnEnabled(); } - + + @Override public void warn(String msg) { delegate().warn(msg); } - + + @Override public void warn(String format, Object arg) { delegate().warn(format, arg); } - + + @Override public void warn(String format, Object arg1, Object arg2) { delegate().warn(format, arg1, arg2); } - + + @Override public void warn(String format, Object... arguments) { delegate().warn(format, arguments); } - + + @Override public void warn(String msg, Throwable t) { delegate().warn(msg, t); } @@ -221,76 +311,105 @@ public class SubstituteLogger implements Logger { public boolean isWarnEnabled(Marker marker) { return delegate().isWarnEnabled(marker); } - + + @Override public void warn(Marker marker, String msg) { delegate().warn(marker, msg); } - + + @Override public void warn(Marker marker, String format, Object arg) { delegate().warn(marker, format, arg); } - + + @Override public void warn(Marker marker, String format, Object arg1, Object arg2) { delegate().warn(marker, format, arg1, arg2); } - + + @Override public void warn(Marker marker, String format, Object... arguments) { delegate().warn(marker, format, arguments); } - + + @Override public void warn(Marker marker, String msg, Throwable t) { delegate().warn(marker, msg, t); } + + @Override + public LoggingEventBuilder atWarn() { + return delegate().atWarn(); + } + + + @Override public boolean isErrorEnabled() { return delegate().isErrorEnabled(); } - + + @Override public void error(String msg) { delegate().error(msg); } - + + @Override public void error(String format, Object arg) { delegate().error(format, arg); } - + + @Override public void error(String format, Object arg1, Object arg2) { delegate().error(format, arg1, arg2); } - + + @Override public void error(String format, Object... arguments) { delegate().error(format, arguments); } - + + @Override public void error(String msg, Throwable t) { delegate().error(msg, t); } - + + @Override public boolean isErrorEnabled(Marker marker) { return delegate().isErrorEnabled(marker); } - + + @Override public void error(Marker marker, String msg) { delegate().error(marker, msg); } - + + @Override public void error(Marker marker, String format, Object arg) { delegate().error(marker, format, arg); } - + + @Override public void error(Marker marker, String format, Object arg1, Object arg2) { delegate().error(marker, format, arg1, arg2); } - + + @Override public void error(Marker marker, String format, Object... arguments) { delegate().error(marker, format, arguments); } - + + @Override public void error(Marker marker, String msg, Throwable t) { delegate().error(marker, msg, t); } @Override + public LoggingEventBuilder atError() { + return delegate().atError(); + } + + @Override public boolean equals(Object o) { if (this == o) return true; @@ -314,8 +433,22 @@ public class SubstituteLogger implements Logger { * Return the delegate logger instance if set. Otherwise, return a {@link NOPLogger} * instance. */ - Logger delegate() { - return _delegate != null ? _delegate : NOPLogger.NOP_LOGGER; + public Logger delegate() { + if (_delegate != null) { + return _delegate; + } + if (createdPostInitialization) { + return NOPLogger.NOP_LOGGER; + } else { + return getEventRecordingLogger(); + } + } + + private Logger getEventRecordingLogger() { + if (eventRecordingLogger == null) { + eventRecordingLogger = new EventRecordingLogger(this, eventQueue); + } + return eventRecordingLogger; } /** @@ -325,4 +458,36 @@ public class SubstituteLogger implements Logger { public void setDelegate(Logger delegate) { this._delegate = delegate; } + + public boolean isDelegateEventAware() { + if (delegateEventAware != null) + return delegateEventAware; + + try { + logMethodCache = _delegate.getClass().getMethod("log", LoggingEvent.class); + delegateEventAware = Boolean.TRUE; + } catch (NoSuchMethodException e) { + delegateEventAware = Boolean.FALSE; + } + return delegateEventAware; + } + + public void log(LoggingEvent event) { + if (isDelegateEventAware()) { + try { + logMethodCache.invoke(_delegate, event); + } catch (IllegalAccessException e) { + } catch (IllegalArgumentException e) { + } catch (InvocationTargetException e) { + } + } + } + + public boolean isDelegateNull() { + return _delegate == null; + } + + public boolean isDelegateNOP() { + return _delegate instanceof NOPLogger; + } } diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java index 0697e39f..8a9d5dd3 100755 --- a/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteLoggerFactory.java @@ -24,13 +24,15 @@ */ package org.slf4j.helpers; -import org.slf4j.ILoggerFactory; -import org.slf4j.Logger; - import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; + +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.event.SubstituteLoggingEvent; /** * SubstituteLoggerFactory manages instances of {@link SubstituteLogger}. @@ -40,28 +42,39 @@ import java.util.concurrent.ConcurrentMap; */ public class SubstituteLoggerFactory implements ILoggerFactory { - final ConcurrentMap<String, SubstituteLogger> loggers = new ConcurrentHashMap<String, SubstituteLogger>(); + volatile boolean postInitialization = false; - public Logger getLogger(String name) { + final Map<String, SubstituteLogger> loggers = new ConcurrentHashMap<>(); + + final LinkedBlockingQueue<SubstituteLoggingEvent> eventQueue = new LinkedBlockingQueue<>(); + + synchronized public Logger getLogger(String name) { SubstituteLogger logger = loggers.get(name); if (logger == null) { - logger = new SubstituteLogger(name); - SubstituteLogger oldLogger = loggers.putIfAbsent(name, logger); - if (oldLogger != null) - logger = oldLogger; + logger = new SubstituteLogger(name, eventQueue, postInitialization); + loggers.put(name, logger); } return logger; } public List<String> getLoggerNames() { - return new ArrayList<String>(loggers.keySet()); + return new ArrayList<>(loggers.keySet()); } public List<SubstituteLogger> getLoggers() { - return new ArrayList<SubstituteLogger>(loggers.values()); + return new ArrayList<>(loggers.values()); + } + + public LinkedBlockingQueue<SubstituteLoggingEvent> getEventQueue() { + return eventQueue; + } + + public void postInitialization() { + postInitialization = true; } public void clear() { loggers.clear(); + eventQueue.clear(); } } diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteServiceProvider.java b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteServiceProvider.java new file mode 100755 index 00000000..2c59530d --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/SubstituteServiceProvider.java @@ -0,0 +1,41 @@ +package org.slf4j.helpers; + +import org.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; + +public class SubstituteServiceProvider implements SLF4JServiceProvider { + private final SubstituteLoggerFactory loggerFactory = new SubstituteLoggerFactory(); + private final IMarkerFactory markerFactory = new BasicMarkerFactory(); + private final MDCAdapter mdcAdapter = new BasicMDCAdapter(); + + @Override + public ILoggerFactory getLoggerFactory() { + return loggerFactory; + } + + public SubstituteLoggerFactory getSubstituteLoggerFactory() { + return loggerFactory; + } + + @Override + public IMarkerFactory getMarkerFactory() { + return markerFactory; + } + + @Override + public MDCAdapter getMDCAdapter() { + return mdcAdapter; + } + + @Override + public String getRequestedApiVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public void initialize() { + + } +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/ThreadLocalMapOfStacks.java b/slf4j-api/src/main/java/org/slf4j/helpers/ThreadLocalMapOfStacks.java new file mode 100644 index 00000000..89ddee0e --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/helpers/ThreadLocalMapOfStacks.java @@ -0,0 +1,89 @@ +package org.slf4j.helpers; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; + + +/** + * A simple implementation of ThreadLocal backed Map containing values of type + * Deque<String>. + * + * @author Ceki Guuml;cü + * @since 2.0.0 + */ +public class ThreadLocalMapOfStacks { + + // BEWARE: Keys or values placed in a ThreadLocal should not be of a type/class + // not included in the JDK. See also https://jira.qos.ch/browse/LOGBACK-450 + + final ThreadLocal<Map<String, Deque<String>>> tlMapOfStacks = new ThreadLocal<>(); + + public void pushByKey(String key, String value) { + if (key == null) + return; + + Map<String, Deque<String>> map = tlMapOfStacks.get(); + + if (map == null) { + map = new HashMap<>(); + tlMapOfStacks.set(map); + } + + Deque<String> deque = map.get(key); + if (deque == null) { + deque = new ArrayDeque<>(); + } + deque.push(value); + map.put(key, deque); + } + + public String popByKey(String key) { + if (key == null) + return null; + + Map<String, Deque<String>> map = tlMapOfStacks.get(); + if (map == null) + return null; + Deque<String> deque = map.get(key); + if (deque == null) + return null; + return deque.pop(); + } + + public Deque<String> getCopyOfDequeByKey(String key) { + if (key == null) + return null; + + Map<String, Deque<String>> map = tlMapOfStacks.get(); + if (map == null) + return null; + Deque<String> deque = map.get(key); + if (deque == null) + return null; + + return new ArrayDeque<String>(deque); + } + + /** + * Clear the deque(stack) referenced by 'key'. + * + * @param key identifies the stack + * + * @since 2.0.0 + */ + public void clearDequeByKey(String key) { + if (key == null) + return; + + Map<String, Deque<String>> map = tlMapOfStacks.get(); + if (map == null) + return; + Deque<String> deque = map.get(key); + if (deque == null) + return; + deque.clear(); + } + +} diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/Util.java b/slf4j-api/src/main/java/org/slf4j/helpers/Util.java index 91a09837..98858315 100755 --- a/slf4j-api/src/main/java/org/slf4j/helpers/Util.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/Util.java @@ -35,6 +35,27 @@ public final class Util { private Util() { } + public static String safeGetSystemProperty(String key) { + if (key == null) + throw new IllegalArgumentException("null input"); + + String result = null; + try { + result = System.getProperty(key); + } catch (java.lang.SecurityException sm) { + ; // ignore + } + return result; + } + + public static boolean safeGetBooleanSystemProperty(String key) { + String value = safeGetSystemProperty(key); + if (value == null) + return false; + else + return value.equalsIgnoreCase("true"); + } + /** * In order to call {@link SecurityManager#getClassContext()}, which is a * protected method, we add this wrapper which allows the method to be visible @@ -46,7 +67,28 @@ public final class Util { } } - private static final ClassContextSecurityManager SECURITY_MANAGER = new ClassContextSecurityManager(); + private static ClassContextSecurityManager SECURITY_MANAGER; + private static boolean SECURITY_MANAGER_CREATION_ALREADY_ATTEMPTED = false; + + private static ClassContextSecurityManager getSecurityManager() { + if (SECURITY_MANAGER != null) + return SECURITY_MANAGER; + else if (SECURITY_MANAGER_CREATION_ALREADY_ATTEMPTED) + return null; + else { + SECURITY_MANAGER = safeCreateSecurityManager(); + SECURITY_MANAGER_CREATION_ALREADY_ATTEMPTED = true; + return SECURITY_MANAGER; + } + } + + private static ClassContextSecurityManager safeCreateSecurityManager() { + try { + return new ClassContextSecurityManager(); + } catch (java.lang.SecurityException sm) { + return null; + } + } /** * Returns the name of the class which called the invoking method. @@ -54,7 +96,10 @@ public final class Util { * @return the name of the class which called the invoking method. */ public static Class<?> getCallingClass() { - Class<?>[] trace = SECURITY_MANAGER.getClassContext(); + ClassContextSecurityManager securityManager = getSecurityManager(); + if (securityManager == null) + return null; + Class<?>[] trace = securityManager.getClassContext(); String thisClassName = Util.class.getName(); // Advance until Util is found @@ -72,13 +117,28 @@ public final class Util { return trace[i + 2]; } + /** + * See {@link Reporter#error(String, Throwable)} class for alternative. + * + * @deprecated replaced by the {@link Reporter#error(String, Throwable)} method. + * @param msg message to print + * @param t throwable to print + */ static final public void report(String msg, Throwable t) { System.err.println(msg); System.err.println("Reported exception:"); t.printStackTrace(); } + /** + * See {@link Reporter} class for alternatives. + * + * @deprecated replaced by one of {@link Reporter#info(String)}, + * {@link Reporter#warn(String)} or {@link Reporter#error(String)} methods. + * @param msg message to print + */ static final public void report(String msg) { System.err.println("SLF4J: " + msg); } + } diff --git a/slf4j-api/src/main/java/org/slf4j/impl/StaticLoggerBinder.java b/slf4j-api/src/main/java/org/slf4j/impl/StaticLoggerBinder.java deleted file mode 100644 index e6747e53..00000000 --- a/slf4j-api/src/main/java/org/slf4j/impl/StaticLoggerBinder.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2004-2011 QOS.ch - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ -package org.slf4j.impl; - -import org.slf4j.ILoggerFactory; - -/** - * The binding of {@link LoggerFactory} class with an actual instance of - * {@link ILoggerFactory} is performed using information returned by this class. - * - * This class is meant to provide a dummy StaticLoggerBinder to the slf4j-api module. - * Real implementations are found in each SLF4J binding project, e.g. slf4j-nop, - * slf4j-log4j12 etc. - * - * @author Ceki Gülcü - */ -public class StaticLoggerBinder { - - /** - * The unique instance of this class. - */ - private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); - - /** - * Return the singleton of this class. - * - * @return the StaticLoggerBinder singleton - */ - public static final StaticLoggerBinder getSingleton() { - return SINGLETON; - } - - /** - * Declare the version of the SLF4J API this implementation is compiled against. - * The value of this field is usually modified with each release. - */ - // to avoid constant folding by the compiler, this field must *not* be final - public static String REQUESTED_API_VERSION = "1.6.99"; // !final - - private StaticLoggerBinder() { - throw new UnsupportedOperationException("This code should have never made it into slf4j-api.jar"); - } - - public ILoggerFactory getLoggerFactory() { - throw new UnsupportedOperationException("This code should never make it into slf4j-api.jar"); - } - - public String getLoggerFactoryClassStr() { - throw new UnsupportedOperationException("This code should never make it into slf4j-api.jar"); - } -} diff --git a/slf4j-api/src/main/java/org/slf4j/impl/StaticMarkerBinder.java b/slf4j-api/src/main/java/org/slf4j/impl/StaticMarkerBinder.java deleted file mode 100644 index 0823b827..00000000 --- a/slf4j-api/src/main/java/org/slf4j/impl/StaticMarkerBinder.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2004-2011 QOS.ch - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ -package org.slf4j.impl; - -import org.slf4j.IMarkerFactory; -import org.slf4j.MarkerFactory; -import org.slf4j.helpers.BasicMarkerFactory; -import org.slf4j.spi.MarkerFactoryBinder; - -/** - * - * The binding of {@link MarkerFactory} class with an actual instance of - * {@link IMarkerFactory} is performed using information returned by this class. - * - * This class is meant to provide a *dummy* StaticMarkerBinder to the slf4j-api module. - * Real implementations are found in each SLF4J binding project, e.g. slf4j-nop, - * slf4j-simple, slf4j-log4j12 etc. - * - * @author Ceki Gülcü - */ -public class StaticMarkerBinder implements MarkerFactoryBinder { - - /** - * The unique instance of this class. - */ - public static final StaticMarkerBinder SINGLETON = new StaticMarkerBinder(); - - private StaticMarkerBinder() { - throw new UnsupportedOperationException("This code should never make it into the jar"); - } - - /** - * Currently this method always returns an instance of - * {@link BasicMarkerFactory}. - */ - public IMarkerFactory getMarkerFactory() { - throw new UnsupportedOperationException("This code should never make it into the jar"); - } - - /** - * Currently, this method returns the class name of - * {@link BasicMarkerFactory}. - */ - public String getMarkerFactoryClassStr() { - throw new UnsupportedOperationException("This code should never make it into the jar"); - } - -} diff --git a/slf4j-api/src/main/java/org/slf4j/impl/package.html b/slf4j-api/src/main/java/org/slf4j/impl/package.html deleted file mode 100644 index 6b84bada..00000000 --- a/slf4j-api/src/main/java/org/slf4j/impl/package.html +++ /dev/null @@ -1,17 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> - - -<html> - <head> - <title></title> - </head> - - - <body> - - <p>Implementations of core logging interfaces defined in the {@link - org.slf4j} package.</p> - - <hr/> - </body> -</html> diff --git a/slf4j-api/src/main/java/org/slf4j/package.html b/slf4j-api/src/main/java/org/slf4j/package.html index e50b3ee2..323bac21 100644 --- a/slf4j-api/src/main/java/org/slf4j/package.html +++ b/slf4j-api/src/main/java/org/slf4j/package.html @@ -11,6 +11,6 @@ <p>Core logging interfaces.</p> - <hr/> + <hr> </body> </html> diff --git a/slf4j-api/src/main/java/org/slf4j/spi/CallerBoundaryAware.java b/slf4j-api/src/main/java/org/slf4j/spi/CallerBoundaryAware.java new file mode 100644 index 00000000..e1a2de95 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/spi/CallerBoundaryAware.java @@ -0,0 +1,25 @@ +package org.slf4j.spi; + +import org.slf4j.event.LoggingEvent; + +/** + * Additional interface to {@link LoggingEventBuilder} and + * {@link org.slf4j.event.LoggingEvent LoggingEvent}. + * + * Implementations of {@link LoggingEventBuilder} and {@link LoggingEvent} may optionally + * implement {@link CallerBoundaryAware} in order to support caller info extraction. + * + * This interface is intended for use by logging backends or logging bridges. + * + * @author Ceki Gulcu + * + */ +public interface CallerBoundaryAware { + + /** + * Add a fqcn (fully qualified class name) to this event, presumed to be the caller boundary. + * + * @param fqcn + */ + void setCallerBoundary(String fqcn); +} diff --git a/slf4j-api/src/main/java/org/slf4j/spi/DefaultLoggingEventBuilder.java b/slf4j-api/src/main/java/org/slf4j/spi/DefaultLoggingEventBuilder.java new file mode 100755 index 00000000..66652b07 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/spi/DefaultLoggingEventBuilder.java @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2004-2022 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.spi; + +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.event.DefaultLoggingEvent; +import org.slf4j.event.KeyValuePair; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; + +/** + * Default implementation of {@link LoggingEventBuilder} + */ +public class DefaultLoggingEventBuilder implements LoggingEventBuilder, CallerBoundaryAware { + + + // The caller boundary when the log() methods are invoked, is this class itself. + + static String DLEB_FQCN = DefaultLoggingEventBuilder.class.getName(); + + protected DefaultLoggingEvent loggingEvent; + protected Logger logger; + + public DefaultLoggingEventBuilder(Logger logger, Level level) { + this.logger = logger; + loggingEvent = new DefaultLoggingEvent(level, logger); + } + + /** + * Add a marker to the current logging event being built. + * + * It is possible to add multiple markers to the same logging event. + * + * @param marker the marker to add + */ + @Override + public LoggingEventBuilder addMarker(Marker marker) { + loggingEvent.addMarker(marker); + return this; + } + + @Override + public LoggingEventBuilder setCause(Throwable t) { + loggingEvent.setThrowable(t); + return this; + } + + @Override + public LoggingEventBuilder addArgument(Object p) { + loggingEvent.addArgument(p); + return this; + } + + @Override + public LoggingEventBuilder addArgument(Supplier<?> objectSupplier) { + loggingEvent.addArgument(objectSupplier.get()); + return this; + } + + @Override + public void setCallerBoundary(String fqcn) { + loggingEvent.setCallerBoundary(fqcn); + } + + @Override + public void log() { + log(loggingEvent); + } + + @Override + public LoggingEventBuilder setMessage(String message) { + loggingEvent.setMessage(message); + return this; + } + @Override + public LoggingEventBuilder setMessage(Supplier<String> messageSupplier) { + loggingEvent.setMessage(messageSupplier.get()); + return this; + } + + @Override + public void log(String message) { + loggingEvent.setMessage(message); + log(loggingEvent); + } + + @Override + public void log(String message, Object arg) { + loggingEvent.setMessage(message); + loggingEvent.addArgument(arg); + log(loggingEvent); + } + + @Override + public void log(String message, Object arg0, Object arg1) { + loggingEvent.setMessage(message); + loggingEvent.addArgument(arg0); + loggingEvent.addArgument(arg1); + log(loggingEvent); + } + + @Override + public void log(String message, Object... args) { + loggingEvent.setMessage(message); + loggingEvent.addArguments(args); + + log(loggingEvent); + } + + @Override + public void log(Supplier<String> messageSupplier) { + if (messageSupplier == null) { + log((String) null); + } else { + log(messageSupplier.get()); + } + } + + protected void log(LoggingEvent aLoggingEvent) { + setCallerBoundary(DLEB_FQCN); + if (logger instanceof LoggingEventAware) { + ((LoggingEventAware) logger).log(aLoggingEvent); + } else { + logViaPublicSLF4JLoggerAPI(aLoggingEvent); + } + } + + private void logViaPublicSLF4JLoggerAPI(LoggingEvent aLoggingEvent) { + Object[] argArray = aLoggingEvent.getArgumentArray(); + int argLen = argArray == null ? 0 : argArray.length; + + Throwable t = aLoggingEvent.getThrowable(); + int tLen = t == null ? 0 : 1; + + String msg = aLoggingEvent.getMessage(); + + Object[] combinedArguments = new Object[argLen + tLen]; + + if (argArray != null) { + System.arraycopy(argArray, 0, combinedArguments, 0, argLen); + } + if (t != null) { + combinedArguments[argLen] = t; + } + + msg = mergeMarkersAndKeyValuePairs(aLoggingEvent, msg); + + switch (aLoggingEvent.getLevel()) { + case TRACE: + logger.trace(msg, combinedArguments); + break; + case DEBUG: + logger.debug(msg, combinedArguments); + break; + case INFO: + logger.info(msg, combinedArguments); + break; + case WARN: + logger.warn(msg, combinedArguments); + break; + case ERROR: + logger.error(msg, combinedArguments); + break; + } + + } + + /** + * Prepend markers and key-value pairs to the message. + * + * @param aLoggingEvent + * @param msg + * @return + */ + private String mergeMarkersAndKeyValuePairs(LoggingEvent aLoggingEvent, String msg) { + + StringBuilder sb = null; + + if (aLoggingEvent.getMarkers() != null) { + sb = new StringBuilder(); + for (Marker marker : aLoggingEvent.getMarkers()) { + sb.append(marker); + sb.append(' '); + } + } + + if (aLoggingEvent.getKeyValuePairs() != null) { + if (sb == null) { + sb = new StringBuilder(); + } + for (KeyValuePair kvp : aLoggingEvent.getKeyValuePairs()) { + sb.append(kvp.key); + sb.append('='); + sb.append(kvp.value); + sb.append(' '); + } + } + + if (sb != null) { + sb.append(msg); + return sb.toString(); + } else { + return msg; + } + } + + + + @Override + public LoggingEventBuilder addKeyValue(String key, Object value) { + loggingEvent.addKeyValue(key, value); + return this; + } + + @Override + public LoggingEventBuilder addKeyValue(String key, Supplier<Object> value) { + loggingEvent.addKeyValue(key, value.get()); + return this; + } + +} diff --git a/slf4j-api/src/main/java/org/slf4j/spi/LocationAwareLogger.java b/slf4j-api/src/main/java/org/slf4j/spi/LocationAwareLogger.java index 8e0a2070..d0d1a31b 100644 --- a/slf4j-api/src/main/java/org/slf4j/spi/LocationAwareLogger.java +++ b/slf4j-api/src/main/java/org/slf4j/spi/LocationAwareLogger.java @@ -34,11 +34,13 @@ import org.slf4j.Marker; * which need to provide hints so that the underlying logging system can extract * the correct location information (method name, line number). * - * @author Ceki Gulcu + * @author Ceki Gülcü * @since 1.3 */ public interface LocationAwareLogger extends Logger { + // these constants should be in EventConstants. However, in order to preserve binary backward compatibility + // we keep these constants here. {@link EventConstants} redefines these constants using the values below. final public int TRACE_INT = 00; final public int DEBUG_INT = 10; final public int INFO_INT = 20; @@ -49,9 +51,12 @@ public interface LocationAwareLogger extends Logger { * Printing method with support for location information. * * @param marker The marker to be used for this event, may be null. + * * @param fqcn The fully qualified class name of the <b>logger instance</b>, * typically the logger class, logger bridge or a logger wrapper. + * * @param level One of the level integers defined in this interface + * * @param message The message for the log event * @param t Throwable associated with the log event, may be null. */ diff --git a/slf4j-api/src/main/java/org/slf4j/spi/LoggerFactoryBinder.java b/slf4j-api/src/main/java/org/slf4j/spi/LoggerFactoryBinder.java index c0c2ca05..169691f3 100644 --- a/slf4j-api/src/main/java/org/slf4j/spi/LoggerFactoryBinder.java +++ b/slf4j-api/src/main/java/org/slf4j/spi/LoggerFactoryBinder.java @@ -31,6 +31,7 @@ import org.slf4j.ILoggerFactory; * class bind with the appropriate {@link ILoggerFactory} instance. * * @author Ceki Gülcü + * @deprecated */ public interface LoggerFactoryBinder { @@ -47,9 +48,9 @@ public interface LoggerFactoryBinder { * The String form of the {@link ILoggerFactory} object that this * <code>LoggerFactoryBinder</code> instance is <em>intended</em> to return. * - * <p>This method allows the developer to intterogate this binder's intention + * <p>This method allows the developer to interrogate this binder's intention * which may be different from the {@link ILoggerFactory} instance it is able to - * yield in practice. The discrepency should only occur in case of errors. + * yield in practice. The discrepancy should only occur in case of errors. * * @return the class name of the intended {@link ILoggerFactory} instance */ diff --git a/slf4j-api/src/main/java/org/slf4j/spi/LoggingEventAware.java b/slf4j-api/src/main/java/org/slf4j/spi/LoggingEventAware.java new file mode 100644 index 00000000..b833e40c --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/spi/LoggingEventAware.java @@ -0,0 +1,23 @@ +package org.slf4j.spi; + +import org.slf4j.event.LoggingEvent; + +/** + * A logger capable of logging from org.slf4j.event.LoggingEvent implements this interface. + * + * <p>Please note that when the {@link #log(LoggingEvent)} method assumes that + * the event was filtered beforehand and no further filtering needs to occur by the method itself. + * <p> + * + * <p>Implementations of this interface <b>may</b> apply further filtering but they are not + * required to do so. In other words, {@link #log(LoggingEvent)} method is free to assume that + * the event was filtered beforehand and no further filtering needs to occur in the method itself.</p> + * + * See also https://jira.qos.ch/browse/SLF4J-575 + * + * @author Ceki Gulcu + * @since 2.0.0 + */ +public interface LoggingEventAware { + void log(LoggingEvent event); +} diff --git a/slf4j-api/src/main/java/org/slf4j/spi/LoggingEventBuilder.java b/slf4j-api/src/main/java/org/slf4j/spi/LoggingEventBuilder.java new file mode 100755 index 00000000..55e24cfb --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/spi/LoggingEventBuilder.java @@ -0,0 +1,169 @@ +/** + * Copyright (c) 2004-2021 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.spi; + +import java.util.function.Supplier; + +import org.slf4j.Marker; + +import org.slf4j.helpers.CheckReturnValue; + +/** + * This is the main interface in slf4j's fluent API for creating + * {@link org.slf4j.event.LoggingEvent logging events}. + * + * @author Ceki Gülcü + * @since 2.0.0 + */ +public interface LoggingEventBuilder { + + /** + * Set the cause for the logging event being built. + * @param cause a throwable + * @return a LoggingEventBuilder, usually <b>this</b>. + */ + @CheckReturnValue + LoggingEventBuilder setCause(Throwable cause); + + /** + * A {@link Marker marker} to the event being built. + * + * @param marker a Marker instance to add. + * @return a LoggingEventBuilder, usually <b>this</b>. + */ + @CheckReturnValue + LoggingEventBuilder addMarker(Marker marker); + + /** + * Add an argument to the event being built. + * + * @param p an Object to add. + * @return a LoggingEventBuilder, usually <b>this</b>. + */ + @CheckReturnValue + LoggingEventBuilder addArgument(Object p); + + /** + * Add an argument supplier to the event being built. + * + * @param objectSupplier an Object supplier to add. + * @return a LoggingEventBuilder, usually <b>this</b>. + */ + @CheckReturnValue + LoggingEventBuilder addArgument(Supplier<?> objectSupplier); + + + /** + * Add a {@link org.slf4j.event.KeyValuePair key value pair} to the event being built. + * + * @param key the key of the key value pair. + * @param value the value of the key value pair. + * @return a LoggingEventBuilder, usually <b>this</b>. + */ + @CheckReturnValue + LoggingEventBuilder addKeyValue(String key, Object value); + + /** + * Add a {@link org.slf4j.event.KeyValuePair key value pair} to the event being built. + * + * @param key the key of the key value pair. + * @param valueSupplier a supplier of a value for the key value pair. + * @return a LoggingEventBuilder, usually <b>this</b>. + */ + @CheckReturnValue + LoggingEventBuilder addKeyValue(String key, Supplier<Object> valueSupplier); + + /** + * Sets the message of the logging event. + * + * @since 2.0.0-beta0 + */ + @CheckReturnValue + LoggingEventBuilder setMessage(String message); + + /** + * Sets the message of the event via a message supplier. + * + * @param messageSupplier supplies a String to be used as the message for the event + * @since 2.0.0-beta0 + */ + @CheckReturnValue + LoggingEventBuilder setMessage(Supplier<String> messageSupplier); + + /** + * After the logging event is built, performs actual logging. This method must be called + * for logging to occur. + * + * If the call to {@link #log()} is omitted, a {@link org.slf4j.event.LoggingEvent LoggingEvent} + * will be built but no logging will occur. + * + * @since 2.0.0-beta0 + */ + void log(); + + /** + * Equivalent to calling {@link #setMessage(String)} followed by {@link #log()}; + * + * @param message the message to log + */ + void log(String message); + + /** + * Equivalent to calling {@link #setMessage(String)} followed by {@link #addArgument(Object)}} + * and then {@link #log()} + * + * @param message the message to log + * @param arg an argument to be used with the message to log + */ + void log(String message, Object arg); + + /** + * Equivalent to calling {@link #setMessage(String)} followed by two calls to + * {@link #addArgument(Object)} and then {@link #log()} + * + * @param message the message to log + * @param arg0 first argument to be used with the message to log + * @param arg1 second argument to be used with the message to log + */ + void log(String message, Object arg0, Object arg1); + + + /** + * Equivalent to calling {@link #setMessage(String)} followed by zero or more calls to + * {@link #addArgument(Object)} (depending on the size of args array) and then {@link #log()} + * + * @param message the message to log + * @param args a list (actually an array) of arguments to be used with the message to log + */ + void log(String message, Object... args); + + /** + * Equivalent to calling {@link #setMessage(Supplier)} followed by {@link #log()} + * + * @param messageSupplier a Supplier returning a message of type String + */ + void log(Supplier<String> messageSupplier); + +} diff --git a/slf4j-api/src/main/java/org/slf4j/spi/MDCAdapter.java b/slf4j-api/src/main/java/org/slf4j/spi/MDCAdapter.java index 2e637cbe..924849d1 100644 --- a/slf4j-api/src/main/java/org/slf4j/spi/MDCAdapter.java +++ b/slf4j-api/src/main/java/org/slf4j/spi/MDCAdapter.java @@ -24,6 +24,7 @@ */ package org.slf4j.spi; +import java.util.Deque; import java.util.Map; /** @@ -38,7 +39,7 @@ public interface MDCAdapter { /** * Put a context value (the <code>val</code> parameter) as identified with * the <code>key</code> parameter into the current thread's context map. - * The <code>key</code> parameter cannot be null. The code>val</code> parameter + * The <code>key</code> parameter cannot be null. The <code>val</code> parameter * can be null only if the underlying implementation supports it. * * <p>If the current thread does not have a context map it is created as a side @@ -55,7 +56,7 @@ public interface MDCAdapter { public String get(String key); /** - * Remove the the context identified by the <code>key</code> parameter. + * Remove the context identified by the <code>key</code> parameter. * The <code>key</code> parameter cannot be null. * * <p> @@ -83,9 +84,50 @@ public interface MDCAdapter { * map and then copying the map passed as parameter. The context map * parameter must only contain keys and values of type String. * + * Implementations must support null valued map passed as parameter. + * * @param contextMap must contain only keys and values of type String * * @since 1.5.1 */ public void setContextMap(Map<String, String> contextMap); + + /** + * Push a value into the deque(stack) referenced by 'key'. + * + * @param key identifies the appropriate stack + * @param value the value to push into the stack + * @since 2.0.0 + */ + public void pushByKey(String key, String value); + + /** + * Pop the stack referenced by 'key' and return the value possibly null. + * + * @param key identifies the deque(stack) + * @return the value just popped. May be null/ + * @since 2.0.0 + */ + public String popByKey(String key); + + /** + * Returns a copy of the deque(stack) referenced by 'key'. May be null. + * + * @param key identifies the stack + * @return copy of stack referenced by 'key'. May be null. + * + * @since 2.0.0 + */ + public Deque<String> getCopyOfDequeByKey(String key); + + + /** + * Clear the deque(stack) referenced by 'key'. + * + * @param key identifies the stack + * + * @since 2.0.0 + */ + public void clearDequeByKey(String key); + } diff --git a/slf4j-api/src/main/java/org/slf4j/spi/MarkerFactoryBinder.java b/slf4j-api/src/main/java/org/slf4j/spi/MarkerFactoryBinder.java index a71140c9..0502ffad 100644 --- a/slf4j-api/src/main/java/org/slf4j/spi/MarkerFactoryBinder.java +++ b/slf4j-api/src/main/java/org/slf4j/spi/MarkerFactoryBinder.java @@ -31,6 +31,7 @@ import org.slf4j.IMarkerFactory; * class bind with the appropriate {@link IMarkerFactory} instance. * * @author Ceki Gülcü + * @deprecated */ public interface MarkerFactoryBinder { @@ -47,9 +48,9 @@ public interface MarkerFactoryBinder { * The String form of the {@link IMarkerFactory} object that this * <code>MarkerFactoryBinder</code> instance is <em>intended</em> to return. * - * <p>This method allows the developer to intterogate this binder's intention + * <p>This method allows the developer to interrogate this binder's intention * which may be different from the {@link IMarkerFactory} instance it is able to - * return. Such a discrepency should only occur in case of errors. + * return. Such a discrepancy should only occur in case of errors. * * @return the class name of the intended {@link IMarkerFactory} instance */ diff --git a/slf4j-api/src/main/java/org/slf4j/spi/NOPLoggingEventBuilder.java b/slf4j-api/src/main/java/org/slf4j/spi/NOPLoggingEventBuilder.java new file mode 100755 index 00000000..e356b47f --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/spi/NOPLoggingEventBuilder.java @@ -0,0 +1,100 @@ +package org.slf4j.spi; + +import java.util.function.Supplier; + +import org.slf4j.Marker; +import org.slf4j.event.Level; + +/** + * <p>A no-operation implementation of {@link LoggingEventBuilder}.</p> + * + * <p>As the name indicates, the method in this class do nothing, except when a return value is expected + * in which case a singleton, i.e. the unique instance of this class is returned. + * </p + * + * @author Ceki Gülcü + * @since 2.0.0 + * + */ +public class NOPLoggingEventBuilder implements LoggingEventBuilder { + + static final NOPLoggingEventBuilder SINGLETON = new NOPLoggingEventBuilder(); + + private NOPLoggingEventBuilder() { + } + + /** + * <p>Returns the singleton instance of this class. + * Used by {@link org.slf4j.Logger#makeLoggingEventBuilder(Level) makeLoggingEventBuilder(Level)}.</p> + * + * @return the singleton instance of this class + */ + public static LoggingEventBuilder singleton() { + return SINGLETON; + } + + @Override + public LoggingEventBuilder addMarker(Marker marker) { + return singleton(); + } + + @Override + public LoggingEventBuilder addArgument(Object p) { + return singleton(); + } + + @Override + public LoggingEventBuilder addArgument(Supplier<?> objectSupplier) { + return singleton(); + } + + @Override + public LoggingEventBuilder addKeyValue(String key, Object value) { + return singleton(); + } + + @Override + public LoggingEventBuilder addKeyValue(String key, Supplier<Object> value) { + return singleton(); + } + + @Override + public LoggingEventBuilder setCause(Throwable cause) { + return singleton(); + } + + @Override + public void log() { + } + + @Override + public LoggingEventBuilder setMessage(String message) { + return this; + } + @Override + public LoggingEventBuilder setMessage(Supplier<String> messageSupplier) { + return this; + } + + @Override + public void log(String message) { + } + + @Override + public void log(Supplier<String> messageSupplier) { + } + + @Override + public void log(String message, Object arg) { + } + + @Override + public void log(String message, Object arg0, Object arg1) { + } + + @Override + public void log(String message, Object... args) { + + } + +} diff --git a/slf4j-api/src/main/java/org/slf4j/spi/SLF4JServiceProvider.java b/slf4j-api/src/main/java/org/slf4j/spi/SLF4JServiceProvider.java new file mode 100755 index 00000000..83e08741 --- /dev/null +++ b/slf4j-api/src/main/java/org/slf4j/spi/SLF4JServiceProvider.java @@ -0,0 +1,60 @@ +package org.slf4j.spi; + +import org.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +/** + * This interface based on {@link java.util.ServiceLoader} paradigm. + * + * <p>It replaces the old static-binding mechanism used in SLF4J versions 1.0.x to 1.7.x. + * + * @author Ceki G¨lc¨ + * @since 1.8 + */ +public interface SLF4JServiceProvider { + + /** + * Return the instance of {@link ILoggerFactory} that + * {@link org.slf4j.LoggerFactory} class should bind to. + * + * @return instance of {@link ILoggerFactory} + */ + public ILoggerFactory getLoggerFactory(); + + /** + * Return the instance of {@link IMarkerFactory} that + * {@link org.slf4j.MarkerFactory} class should bind to. + * + * @return instance of {@link IMarkerFactory} + */ + public IMarkerFactory getMarkerFactory(); + + /** + * Return the instance of {@link MDCAdapter} that + * {@link MDC} should bind to. + * + * @return instance of {@link MDCAdapter} + */ + public MDCAdapter getMDCAdapter(); + + /** + * Return the maximum API version for SLF4J that the logging + * implementation supports. + * + * <p>For example: {@code "2.0.1"}. + * + * @return the string API version. + */ + public String getRequestedApiVersion(); + + /** + * Initialize the logging back-end. + * + * <p><b>WARNING:</b> This method is intended to be called once by + * {@link LoggerFactory} class and from nowhere else. + * + */ + public void initialize(); +} diff --git a/slf4j-api/src/main/java9/module-info.java b/slf4j-api/src/main/java9/module-info.java new file mode 100755 index 00000000..96f08f29 --- /dev/null +++ b/slf4j-api/src/main/java9/module-info.java @@ -0,0 +1,8 @@ +module org.slf4j { + exports org.slf4j; + exports org.slf4j.spi; + exports org.slf4j.event; + exports org.slf4j.helpers; + uses org.slf4j.spi.SLF4JServiceProvider; +requires java.base; +} diff --git a/slf4j-api/src/main/resources/META-INF/MANIFEST.MF b/slf4j-api/src/main/resources/META-INF/MANIFEST.MF deleted file mode 100644 index 2940ef8f..00000000 --- a/slf4j-api/src/main/resources/META-INF/MANIFEST.MF +++ /dev/null @@ -1,8 +0,0 @@ -Implementation-Title: slf4j-api -Bundle-ManifestVersion: 2 -Bundle-SymbolicName: slf4j.api -Bundle-Name: slf4j-api -Bundle-Vendor: SLF4J.ORG -Bundle-RequiredExecutionEnvironment: J2SE-1.5 -Export-Package: org.slf4j;version=${parsedVersion.osgiVersion}, org.slf4j.spi;version=${parsedVersion.osgiVersion}, org.slf4j.helpers;version=${parsedVersion.osgiVersion} -Import-Package: org.slf4j.impl;version=${slf4j.api.minimum.compatible.version} diff --git a/slf4j-api/src/test/java/org/slf4j/LoggerAccessingThread.java b/slf4j-api/src/test/java/org/slf4j/LoggerAccessingThread.java new file mode 100755 index 00000000..30ab5705 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/LoggerAccessingThread.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2004-2016 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j; + +import java.util.List; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicLong; + +public class LoggerAccessingThread extends Thread { + private static final int LOOP_LEN = 32; + + final CyclicBarrier barrier; + final int count; + final AtomicLong eventCount; + List<Logger> loggerList; + + public LoggerAccessingThread(final CyclicBarrier barrier, List<Logger> loggerList, final int count, final AtomicLong eventCount) { + this.barrier = barrier; + this.loggerList = loggerList; + this.count = count; + this.eventCount = eventCount; + } + + public void run() { + try { + barrier.await(); + } catch (Exception e) { + e.printStackTrace(); + } + + String loggerNamePrefix = this.getClass().getName(); + for (int i = 0; i < LOOP_LEN; i++) { + Logger logger = LoggerFactory.getLogger(loggerNamePrefix + "-" + count + "-" + i); + loggerList.add(logger); + Thread.yield(); + logger.info("in run method"); + eventCount.getAndIncrement(); + } + } +} diff --git a/slf4j-api/src/test/java/org/slf4j/LoggerFactoryTest.java b/slf4j-api/src/test/java/org/slf4j/LoggerFactoryTest.java new file mode 100644 index 00000000..ed618171 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/LoggerFactoryTest.java @@ -0,0 +1,91 @@ +package org.slf4j; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class LoggerFactoryTest { + private PrintStream rawSyserr; + private ByteArrayOutputStream mockedSyserr; + + final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader(); + + @Before + public void setUp() { + rawSyserr = System.err; + mockedSyserr = new ByteArrayOutputStream(); + System.setErr(new PrintStream(mockedSyserr)); + } + + @After + public void cleanUp() { + System.clearProperty(LoggerFactory.PROVIDER_PROPERTY_KEY); + System.setErr(rawSyserr); + } + + @Test + public void testExplicitlySpecified() { + System.setProperty(LoggerFactory.PROVIDER_PROPERTY_KEY, "org.slf4j.LoggerFactoryTest$TestingProvider"); + SLF4JServiceProvider provider = LoggerFactory.loadExplicitlySpecified(classLoaderOfLoggerFactory); + assertTrue("provider should be instance of TestingProvider class", provider instanceof TestingProvider); + assertTrue(mockedSyserr.toString().contains(" Attempting to load provider \"org.slf4j.LoggerFactoryTest$TestingProvider\" specified via \"slf4j.provider\" system property")); + System.out.println(mockedSyserr.toString()); + + + } + + @Test + public void testExplicitlySpecifiedNull() { + assertNull(LoggerFactory.loadExplicitlySpecified(classLoaderOfLoggerFactory)); + } + + @Test + public void testExplicitlySpecifyMissingServiceProvider() { + System.setProperty(LoggerFactory.PROVIDER_PROPERTY_KEY, "com.example.ServiceProvider"); + SLF4JServiceProvider provider = LoggerFactory.loadExplicitlySpecified(classLoaderOfLoggerFactory); + assertNull(provider); + assertTrue(mockedSyserr.toString().contains("Failed to instantiate the specified SLF4JServiceProvider (com.example.ServiceProvider)")); + } + + @Test + public void testExplicitlySpecifyNonServiceProvider() { + System.setProperty(LoggerFactory.PROVIDER_PROPERTY_KEY, "java.lang.String"); + assertNull(LoggerFactory.loadExplicitlySpecified(classLoaderOfLoggerFactory)); + assertTrue(mockedSyserr.toString().contains("Specified SLF4JServiceProvider (java.lang.String) does not implement SLF4JServiceProvider interface")); + } + + public static class TestingProvider implements SLF4JServiceProvider { + @Override + public ILoggerFactory getLoggerFactory() { + return null; + } + + @Override + public IMarkerFactory getMarkerFactory() { + return null; + } + + @Override + public MDCAdapter getMDCAdapter() { + return null; + } + + @Override + public String getRequestedApiVersion() { + return null; + } + + @Override + public void initialize() { + + } + } +} diff --git a/slf4j-api/src/test/java/org/slf4j/BasicMarkerTest.java b/slf4j-api/src/test/java/org/slf4j/basicTests/BasicMarkerTest.java index 50471b59..5ce33471 100644 --- a/slf4j-api/src/test/java/org/slf4j/BasicMarkerTest.java +++ b/slf4j-api/src/test/java/org/slf4j/basicTests/BasicMarkerTest.java @@ -22,12 +22,15 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ -package org.slf4j; +package org.slf4j.basicTests; -import java.util.Iterator; +import static org.junit.Assert.*; -import junit.framework.TestCase; +import java.util.Iterator; +import org.junit.Test; +import org.slf4j.IMarkerFactory; +import org.slf4j.Marker; import org.slf4j.helpers.BasicMarkerFactory; /** @@ -36,7 +39,7 @@ import org.slf4j.helpers.BasicMarkerFactory; * @author Ceki Gülcü * @author Joern Huxhorn */ -public class BasicMarkerTest extends TestCase { +public class BasicMarkerTest { static final String BLUE_STR = "BLUE"; static final String RED_STR = "RED"; static final String GREEN_STR = "GREEN"; @@ -69,6 +72,7 @@ public class BasicMarkerTest extends TestCase { multiComp.add(comp); } + @Test public void testPrimitive() { assertEquals(BLUE_STR, blue.getName()); assertTrue(blue.contains(blue)); @@ -80,20 +84,24 @@ public class BasicMarkerTest extends TestCase { assertTrue(blue2.contains(blue)); } + @Test public void testPrimitiveByName() { assertTrue(blue.contains(BLUE_STR)); } + @Test public void testComposite() { assertTrue(comp.contains(comp)); assertTrue(comp.contains(blue)); } + @Test public void testCompositeByName() { assertTrue(comp.contains(COMP_STR)); assertTrue(comp.contains(BLUE_STR)); } + @Test public void testMultiComposite() { assertTrue(multiComp.contains(comp)); assertTrue(multiComp.contains(blue)); @@ -101,6 +109,7 @@ public class BasicMarkerTest extends TestCase { assertFalse(multiComp.contains(red)); } + @Test public void testMultiCompositeByName() { assertTrue(multiComp.contains(COMP_STR)); assertTrue(multiComp.contains(BLUE_STR)); @@ -108,6 +117,7 @@ public class BasicMarkerTest extends TestCase { assertFalse(multiComp.contains(RED_STR)); } + @Test public void testMultiAdd() { Marker parent = factory.getMarker(PARENT_MARKER_STR); Marker child = factory.getMarker(CHILD_MARKER_STR); @@ -122,6 +132,7 @@ public class BasicMarkerTest extends TestCase { assertFalse(iterator.hasNext()); } + @Test public void testAddRemove() { final String NEW_PREFIX = "NEW_"; Marker parent = factory.getMarker(NEW_PREFIX + PARENT_MARKER_STR); @@ -142,6 +153,7 @@ public class BasicMarkerTest extends TestCase { assertFalse(parent.remove(child)); } + @Test public void testSelfRecursion() { final String diffPrefix = "NEW_" + diff; final String PARENT_NAME = diffPrefix + PARENT_MARKER_STR; @@ -155,6 +167,7 @@ public class BasicMarkerTest extends TestCase { assertFalse(parent.contains(NOT_CONTAINED_MARKER_STR)); } + @Test public void testIndirectRecursion() { final String diffPrefix = "NEW_" + diff; final String PARENT_NAME = diffPrefix + PARENT_MARKER_STR; @@ -175,6 +188,7 @@ public class BasicMarkerTest extends TestCase { assertFalse(parent.contains(NOT_CONTAINED_MARKER_STR)); } + @Test public void testHomonyms() { final String diffPrefix = "homonym" + diff; final String PARENT_NAME = diffPrefix + PARENT_MARKER_STR; diff --git a/slf4j-api/src/test/java/org/slf4j/Differentiator.java b/slf4j-api/src/test/java/org/slf4j/basicTests/Differentiator.java index 09b9a35a..fe2a72ee 100644 --- a/slf4j-api/src/test/java/org/slf4j/Differentiator.java +++ b/slf4j-api/src/test/java/org/slf4j/basicTests/Differentiator.java @@ -23,7 +23,7 @@ * */ -package org.slf4j; +package org.slf4j.basicTests; import java.util.Random; diff --git a/slf4j-api/src/test/java/org/slf4j/basicTests/DoubleCheckedInt.java b/slf4j-api/src/test/java/org/slf4j/basicTests/DoubleCheckedInt.java new file mode 100755 index 00000000..42a39e64 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/basicTests/DoubleCheckedInt.java @@ -0,0 +1,141 @@ +package org.slf4j.basicTests; + +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + +/** + * This class demonstrates that threads accessing the STATE variable always see a consistent value. + * + * During ongoing initialization the observed value is either ONGOING_INITIALIZATION + * or one of {SUCCESS, FAILURE}. + * + * Post initialization the observed value is always one of {SUCCESS, FAILURE}. + * + * See also http://jira.qos.ch/browse/SLF4J-167 + * + * @author ceki + * + */ +public class DoubleCheckedInt { + + final static int THREAD_COUNT = 10 + Runtime.getRuntime().availableProcessors() * 2; + final static int UNINITIALIZED_STATE = 0; + final static int ONGOING_INITIALIZATION = 1; + final static int SUCCESS = 2; + final static int FAILURE = 3; + final static int NUMBER_OF_STATES = FAILURE + 1; + + private static int STATE = UNINITIALIZED_STATE; + + public static int getState() { + if (STATE == 0) { + synchronized (DoubleCheckedInt.class) { + if (STATE == UNINITIALIZED_STATE) { + STATE = ONGOING_INITIALIZATION; + long r = System.nanoTime(); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + if (r % 2 == 0) { + STATE = SUCCESS; + } else { + STATE = FAILURE; + } + } + } + } + return STATE; + } + + static public void main(String[] args) throws InterruptedException, BrokenBarrierException { + StateAccessingThread[] preInitializationThreads = harness(); + check(preInitializationThreads, false); + + System.out.println("============"); + StateAccessingThread[] postInitializationThreads = harness(); + check(postInitializationThreads, true); + } + + private static StateAccessingThread[] harness() throws InterruptedException, BrokenBarrierException { + StateAccessingThread[] threads = new StateAccessingThread[THREAD_COUNT]; + final CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT + 1); + for (int i = 0; i < THREAD_COUNT; i++) { + threads[i] = new StateAccessingThread(barrier); + threads[i].start(); + } + + barrier.await(); + for (int i = 0; i < THREAD_COUNT; i++) { + threads[i].join(); + } + return threads; + } + + private static void check(StateAccessingThread[] threads, boolean postInit) { + + int[] stateCount = getStateCount(threads); + printStateCount(stateCount); + + if (stateCount[UNINITIALIZED_STATE] != 0) { + throw new IllegalStateException("getState() should never return a zero value"); + } + + if (stateCount[SUCCESS] != 0 && stateCount[FAILURE] != 0) { + throw new IllegalStateException("getState() should return consistent values"); + } + + if (postInit) { + if (stateCount[SUCCESS] != THREAD_COUNT && stateCount[FAILURE] != THREAD_COUNT) { + throw new IllegalStateException("getState() should return consistent values"); + } + } + + } + + private static void printStateCount(int[] stateCount) { + for (int i = 0; i < NUMBER_OF_STATES; i++) { + switch (i) { + case UNINITIALIZED_STATE: + System.out.println("UNINITIALIZED_STATE count: " + stateCount[i]); + break; + case ONGOING_INITIALIZATION: + System.out.println("ONGOING_INITIALIZATION count: " + stateCount[i]); + break; + case SUCCESS: + System.out.println("SUCCESS count: " + stateCount[i]); + break; + case FAILURE: + System.out.println("FAILURE count: " + stateCount[i]); + break; + } + } + } + + private static int[] getStateCount(StateAccessingThread[] threads) { + int[] valCount = new int[NUMBER_OF_STATES]; + for (StateAccessingThread thread : threads) { + int val = thread.state; + valCount[val] = valCount[val] + 1; + } + return valCount; + } + + static class StateAccessingThread extends Thread { + public int state = -1; + final CyclicBarrier barrier; + + StateAccessingThread(CyclicBarrier barrier) { + this.barrier = barrier; + } + + public void run() { + try { + barrier.await(); + } catch (Exception e) { + e.printStackTrace(); + } + state = DoubleCheckedInt.getState(); + } + }; +} diff --git a/slf4j-api/src/test/java/org/slf4j/basicTests/FluentAPIUsage.java b/slf4j-api/src/test/java/org/slf4j/basicTests/FluentAPIUsage.java new file mode 100755 index 00000000..cc8434a9 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/basicTests/FluentAPIUsage.java @@ -0,0 +1,27 @@ +package org.slf4j.basicTests; + +import static org.junit.Assert.assertFalse; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +public class FluentAPIUsage { + + @Test + public void smoke() { + String name = "smoke"; + Logger logger = LoggerFactory.getLogger(name); + logger.atTrace().addKeyValue("a", "n").setCause(new Throwable()).log("hello"); + } + + + @Test + public void smokxce() { + String name = "smoke"; + Logger logger = LoggerFactory.getLogger(name); + assertFalse(logger.isEnabledForLevel(Level.DEBUG)); + } + +} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/MyRandom.java b/slf4j-api/src/test/java/org/slf4j/basicTests/NoBindingMultithreadedInitializationTest.java index c817d673..024a7f82 100644 --- a/slf4j-api/src/test/java/org/slf4j/helpers/MyRandom.java +++ b/slf4j-api/src/test/java/org/slf4j/basicTests/NoBindingMultithreadedInitializationTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2004-2011 QOS.ch + * Copyright (c) 2004-2016 QOS.ch * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining @@ -22,30 +22,35 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ -package org.slf4j.helpers; +package org.slf4j.basicTests; -class MyRandom { +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactoryFriend; +import org.slf4j.testHarness.MultithreadedInitializationTest; - private final static long m = 200000000041L; // a prime number - private final static long a = 2000000011L; // a prime number - - long y; - long unused; - int bits = 32; +/** + * Checks that when no binding is present, proper clean up is performed by LoggerFactory. + * + * See SLF4J-469 + * + * @author David Harsha + */ +public class NoBindingMultithreadedInitializationTest extends MultithreadedInitializationTest { + final String loggerName = this.getClass().getName(); - public MyRandom() { - this(System.nanoTime()); + @Before + public void setup() { + LoggerFactoryFriend.reset(); } - public MyRandom(long seed) { - this.y = seed; + @After + public void tearDown() throws Exception { + LoggerFactoryFriend.reset(); } - int nextInt() { - // we don't really care about the randomness of this - // generator - y = (a * y + 1) % m; - unused = y >>> (48 - bits); // just exercise the >>> operator - return (int) (y); + @Override + protected long getRecordedEventCount() { + return eventCount.get(); } } diff --git a/slf4j-api/src/test/java/org/slf4j/NoBindingTest.java b/slf4j-api/src/test/java/org/slf4j/basicTests/NoBindingTest.java index 7a5ec2d8..8e1dd16c 100644 --- a/slf4j-api/src/test/java/org/slf4j/NoBindingTest.java +++ b/slf4j-api/src/test/java/org/slf4j/basicTests/NoBindingTest.java @@ -22,30 +22,40 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ -package org.slf4j; +package org.slf4j.basicTests; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.util.Random; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; import org.slf4j.helpers.BasicMarker; import org.slf4j.helpers.NOPLogger; -import junit.framework.TestCase; - -public class NoBindingTest extends TestCase { +public class NoBindingTest { int diff = new Random().nextInt(10000); + @Test public void testLogger() { Logger logger = LoggerFactory.getLogger(NoBindingTest.class); logger.debug("hello" + diff); assertTrue(logger instanceof NOPLogger); } + @Test public void testMDC() { MDC.put("k" + diff, "v"); assertNull(MDC.get("k")); } + @Test public void testMarker() { Marker m = MarkerFactory.getMarker("a" + diff); assertTrue(m instanceof BasicMarker); diff --git a/slf4j-api/src/test/java/org/slf4j/eventTest/EventRecordingLoggerTest.java b/slf4j-api/src/test/java/org/slf4j/eventTest/EventRecordingLoggerTest.java new file mode 100644 index 00000000..e2ec1c05 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/eventTest/EventRecordingLoggerTest.java @@ -0,0 +1,531 @@ +package org.slf4j.eventTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Marker; +import org.slf4j.event.EventRecordingLogger; +import org.slf4j.event.Level; +import org.slf4j.event.SubstituteLoggingEvent; +import org.slf4j.helpers.BasicMarkerFactory; +import org.slf4j.helpers.SubstituteLogger; + +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +import static org.junit.Assert.*; + +public class EventRecordingLoggerTest { + private Queue<SubstituteLoggingEvent> queue; + private EventRecordingLogger logger; + private String message; + private Object param1; + private Object param2; + private Object param3; + private Object[] oneParam; + private Object[] twoParams; + private Object[] threeParams; + private Throwable exception; + private Marker marker; + + @Before + public void setUp() { + queue = new LinkedBlockingQueue<>(); + logger = new EventRecordingLogger(new SubstituteLogger("testLogger", queue, true), queue); + message = "Test message with 3 parameters {} {} {} {}"; + param1 = 1; + param2 = 2; + param3 = 3; + oneParam = new Object[] { param1 }; + twoParams = new Object[] { param1, param2 }; + threeParams = new Object[] { param1, param2, param3 }; + exception = new IllegalStateException("We just need an exception"); + marker = new BasicMarkerFactory().getMarker("testMarker"); + } + + @After + public void tearDown() { + assertTrue(queue.isEmpty()); + } + + @Test + public void singleMessage() { + for (Level level : Level.values()) { + singleMessageCheck(level); + } + } + + private void singleMessageCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(message); + break; + case DEBUG: + logger.debug(message); + break; + case INFO: + logger.info(message); + break; + case WARN: + logger.warn(message); + break; + case ERROR: + logger.error(message); + break; + } + verifyMessageWithoutMarker(level, null, null); + } + + @Test + public void oneParameter() { + for (Level level : Level.values()) { + oneParameterCheck(level); + } + } + + private void oneParameterCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(message, param1); + break; + case DEBUG: + logger.debug(message, param1); + break; + case INFO: + logger.info(message, param1); + break; + case WARN: + logger.warn(message, param1); + break; + case ERROR: + logger.error(message, param1); + break; + } + verifyMessageWithoutMarker(level, oneParam, null); + } + + @Test + public void messageTwoParameters() { + for (Level level : Level.values()) { + messageTwoParametersCheck(level); + } + } + + private void messageTwoParametersCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(message, param1, param2); + break; + case DEBUG: + logger.debug(message, param1, param2); + break; + case INFO: + logger.info(message, param1, param2); + break; + case WARN: + logger.warn(message, param1, param2); + break; + case ERROR: + logger.error(message, param1, param2); + break; + } + verifyMessageWithoutMarker(level, twoParams, null); + } + + @Test + public void traceMessageThreeParameters() { + for (Level level : Level.values()) { + threeParameterCheck(level); + } + } + + private void threeParameterCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(message, param1, param2, param3); + break; + case DEBUG: + logger.debug(message, param1, param2, param3); + break; + case INFO: + logger.info(message, param1, param2, param3); + break; + case WARN: + logger.warn(message, param1, param2, param3); + break; + case ERROR: + logger.error(message, param1, param2, param3); + break; + } + verifyMessageWithoutMarker(level, threeParams, null); + } + + @Test + public void testMessageThrowable() { + for (Level level : Level.values()) { + throwableCheck(level); + } + } + + private void throwableCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(message, exception); + break; + case DEBUG: + logger.debug(message, exception); + break; + case INFO: + logger.info(message, exception); + break; + case WARN: + logger.warn(message, exception); + break; + case ERROR: + logger.error(message, exception); + break; + } + verifyMessageWithoutMarker(level, null, exception); + } + + @Test + public void traceMessageOneParameterThrowable() { + for (Level level : Level.values()) { + oneParamThrowableCheck(level); + } + } + + private void oneParamThrowableCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(message, param1, exception); + break; + case DEBUG: + logger.debug(message, param1, exception); + break; + case INFO: + logger.info(message, param1, exception); + break; + case WARN: + logger.warn(message, param1, exception); + break; + case ERROR: + logger.error(message, param1, exception); + break; + } + verifyMessageWithoutMarker(level, oneParam, exception); + } + + @Test + public void traceMessageTwoParametersThrowable() { + for (Level level : Level.values()) { + twoParamThrowableCheck(level); + } + } + + private void twoParamThrowableCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(message, param1, param2, exception); + break; + case DEBUG: + logger.debug(message, param1, param2, exception); + break; + case INFO: + logger.info(message, param1, param2, exception); + break; + case WARN: + logger.warn(message, param1, param2, exception); + break; + case ERROR: + logger.error(message, param1, param2, exception); + break; + } + verifyMessageWithoutMarker(level, twoParams, exception); + } + + @Test + public void testMessageThreeParametersThrowable() { + for (Level level : Level.values()) { + messageWith3ArgsPlusException(level); + } + } + + private void messageWith3ArgsPlusException(Level level) { + switch (level) { + case TRACE: + logger.trace(message, param1, param2, param3, exception); + break; + case DEBUG: + logger.debug(message, param1, param2, param3, exception); + break; + case INFO: + logger.info(message, param1, param2, param3, exception); + break; + case WARN: + logger.warn(message, param1, param2, param3, exception); + break; + case ERROR: + logger.error(message, param1, param2, param3, exception); + break; + } + verifyMessageWithoutMarker(level, threeParams, exception); + } + + @Test + public void markerMessage() { + for (Level level : Level.values()) { + markerMessageCheck(level); + } + } + + private void markerMessageCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message); + break; + case DEBUG: + logger.debug(marker, message); + break; + case INFO: + logger.info(marker, message); + break; + case WARN: + logger.warn(marker, message); + break; + case ERROR: + logger.error(marker, message); + break; + } + verifyMessage(level, marker, null, null); + } + + @Test + public void markerMessageOneParameter() { + for (Level level : Level.values()) { + markerMessageOneParameter(level); + } + } + + private void markerMessageOneParameter(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message, param1); + break; + case DEBUG: + logger.debug(marker, message, param1); + break; + case INFO: + logger.info(marker, message, param1); + break; + case WARN: + logger.warn(marker, message, param1); + break; + case ERROR: + logger.error(marker, message, param1); + break; + } + verifyMessage(level, marker, oneParam, null); + } + + @Test + public void traceMarkerMessageTwoParameters() { + for (Level level : Level.values()) { + markerMessageTwoParameters(level); + } + } + + private void markerMessageTwoParameters(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message, param1, param2); + break; + case DEBUG: + logger.debug(marker, message, param1, param2); + break; + case INFO: + logger.info(marker, message, param1, param2); + break; + case WARN: + logger.warn(marker, message, param1, param2); + break; + case ERROR: + logger.error(marker, message, param1, param2); + break; + } + verifyMessage(level, marker, twoParams, null); + } + + @Test + public void traceMarkerMessageThreeParameters() { + for (Level level : Level.values()) { + markerMessageThreeParameters(level); + } + } + + private void markerMessageThreeParameters(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message, param1, param2, param3); + break; + case DEBUG: + logger.debug(marker, message, param1, param2, param3); + break; + case INFO: + logger.info(marker, message, param1, param2, param3); + break; + case WARN: + logger.warn(marker, message, param1, param2, param3); + break; + case ERROR: + logger.error(marker, message, param1, param2, param3); + break; + } + verifyMessage(level, marker, threeParams, null); + } + + @Test + public void markerMessageThrowable() { + for (Level level : Level.values()) { + markerMessageThrowable(level); + } + } + + private void markerMessageThrowable(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message, exception); + break; + case DEBUG: + logger.debug(marker, message, exception); + break; + case INFO: + logger.info(marker, message, exception); + break; + case WARN: + logger.warn(marker, message, exception); + break; + case ERROR: + logger.error(marker, message, exception); + break; + } + verifyMessage(level, marker, null, exception); + } + + @Test + public void markerMessageOneParameterThrowable() { + for (Level level : Level.values()) { + markerMessageOneParameterThrowableCheck(level); + } + } + + private void markerMessageOneParameterThrowableCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message, param1, exception); + break; + case DEBUG: + logger.debug(marker, message, param1, exception); + break; + case INFO: + logger.info(marker, message, param1, exception); + break; + case WARN: + logger.warn(marker, message, param1, exception); + break; + case ERROR: + logger.error(marker, message, param1, exception); + break; + } + verifyMessage(level, marker, oneParam, exception); + } + + @Test + public void traceMarkerMessageTwoParametersThrowable() { + for (Level level : Level.values()) { + markerMessageTwoParametersThrowableCheck(level); + } + } + + private void markerMessageTwoParametersThrowableCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message, param1, param2, exception); + break; + case DEBUG: + logger.debug(marker, message, param1, param2, exception); + break; + case INFO: + logger.info(marker, message, param1, param2, exception); + break; + case WARN: + logger.warn(marker, message, param1, param2, exception); + break; + case ERROR: + logger.error(marker, message, param1, param2, exception); + break; + } + verifyMessage(level, marker, twoParams, exception); + } + + @Test + public void traceMarkerMessageThreeParametersThrowable() { + for (Level level : Level.values()) { + markerMessageThreeParametersThrowableCheck(level); + } + } + + private void markerMessageThreeParametersThrowableCheck(Level level) { + switch (level) { + case TRACE: + logger.trace(marker, message, param1, param2, param3, exception); + break; + case DEBUG: + logger.debug(marker, message, param1, param2, param3, exception); + break; + case INFO: + logger.info(marker, message, param1, param2, param3, exception); + break; + case WARN: + logger.warn(marker, message, param1, param2, param3, exception); + break; + case ERROR: + logger.error(marker, message, param1, param2, param3, exception); + break; + } + verifyMessage(level, marker, threeParams, exception); + } + + private void verifyMessageWithoutMarker(Level level, Object[] arguments, Throwable exception) { + verifyMessage(level, null, arguments, exception); + } + + private void verifyMessage(Level level, Marker marker, Object[] arguments, Throwable exception) { + + assertEquals("missing event: ", 1, queue.size()); + SubstituteLoggingEvent event = queue.poll(); + assertNotNull(event); + + if (marker == null) { + assertNull(event.getMarkers()); + } else { + assertEquals(marker, event.getMarkers().get(0)); + } + + assertEquals(message, event.getMessage()); + + if (arguments == null) { + assertNull(event.getArgumentArray()); + } else { + assertArrayEquals(arguments, event.getArgumentArray()); + } + + assertEquals("wrong level: ", level, event.getLevel()); + + if (exception == null) { + assertNull(event.getThrowable()); + } else { + assertEquals(exception, event.getThrowable()); + } + } +} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/BasicMDCAdapterTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/BasicMDCAdapterTest.java new file mode 100755 index 00000000..f73a6e06 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/helpers/BasicMDCAdapterTest.java @@ -0,0 +1,17 @@ +package org.slf4j.helpers; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class BasicMDCAdapterTest extends MDCAdapterTestBase { + + @Test + public void testClearingMDC() { + mdc.put("testKey", "testValue"); + assertFalse(mdc.getCopyOfContextMap().isEmpty()); + mdc.clear(); + assertNull(mdc.getCopyOfContextMap()); + } +} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/BogoPerf.java b/slf4j-api/src/test/java/org/slf4j/helpers/BogoPerf.java deleted file mode 100644 index 86f7d67d..00000000 --- a/slf4j-api/src/test/java/org/slf4j/helpers/BogoPerf.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) 2004-2011 QOS.ch - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ -package org.slf4j.helpers; - -import junit.framework.AssertionFailedError; - -/** - * BogoPerf is used to check that the time required to perform a certain - * operation does not deteriorate over time. BogoPerf adjusts to the CPU speed - * and capabilities of the host. - * - * @author Ceki Gülcü - * - */ -public class BogoPerf { - - private static long NANOS_IN_ONE_SECOND = 1000 * 1000 * 1000; - private static int INITIAL_N = 1000; - private static int LAST_N = 100; - private static int SLACK_FACTOR = 3; - - static { - // let the JIT warm up - computeBogoIPS(INITIAL_N); - double bogo_ips = computeBogoIPS(INITIAL_N); - System.out.println("Host runs at " + bogo_ips + " BIPS"); - } - - /** - * Compute bogoInstructions per second - * <p> - * on a 3.2 Ghz Pentium D CPU (around 2007), we obtain about 9'000 bogoIPS. - * - * @param N - * number of bogoInstructions to average over in order to - * compute the result - * @return bogo Instructions Per Second - */ - private static double computeBogoIPS(int N) { - long begin = System.nanoTime(); - - for (int i = 0; i < N; i++) { - bogoInstruction(); - } - long end = System.nanoTime(); - - // duration - double D = end - begin; - // average duration per instruction - double avgDPIS = D / N; - // System.out.println(D + " nanos for " + N + " instructions"); - // System.out.println(avgD + " nanos per instruction"); - - double bogoIPS = NANOS_IN_ONE_SECOND / avgDPIS; - // System.out.println(bogoIPS + " bogoIPS"); - - return bogoIPS; - } - - private static void bogoInstruction() { - // use our own random number generator, independent of the host JDK - MyRandom myRandom = new MyRandom(100); - int len = 150; - int[] intArray = new int[len]; - for (int i = 0; i < len; i++) { - intArray[i] = myRandom.nextInt(); - } - // use our own sort algorithm, independent of the host JDK - BubbleSort.sort(intArray); - } - - /** - * Computed the BogoIPS for this host CPU. - * - * @return - */ - public static double currentBIPS() { - return computeBogoIPS(LAST_N); - } - - static double min(double a, double b) { - return (a <= b) ? a : b; - } - - /** - * Assertion used for values that <b>decrease</b> with faster CPUs, typically - * the time (duration) needed to perform a task. - * - * @param currentDuration - * @param referenceDuration - * @param referenceBIPS - * @throws AssertionFailedError - */ - public static void assertDuration(double currentDuration, long referenceDuration, double referenceBIPS) throws AssertionFailedError { - double ajustedDuration = adjustExpectedDuration(referenceDuration, referenceBIPS); - if (currentDuration > ajustedDuration * SLACK_FACTOR) { - throw new AssertionFailedError("current duration " + currentDuration + " exceeded expected " + ajustedDuration + " (adjusted reference), " - + referenceDuration + " (raw reference)"); - } - } - - /** - * Assertion used for values that <b>increase<b> with faster CPUs, typically - * the number of operations accomplished per unit of time. - * - * @param currentPerformance - * @param referencePerformance - * @param referenceBIPS - * @throws AssertionFailedError - */ - public static void assertPerformance(double currentPerformance, long referencePerformance, double referenceBIPS) throws AssertionFailedError { - double ajustedPerf = adjustExpectedPerformance(referencePerformance, referenceBIPS); - if (currentPerformance * SLACK_FACTOR < ajustedPerf) { - throw new AssertionFailedError(currentPerformance + " below expected " + ajustedPerf + " (adjusted), " + referencePerformance + " (raw)"); - } - } - - private static double adjustExpectedPerformance(long referenceDuration, double referenceBIPS) { - double currentBIPS = currentBIPS(); - return referenceDuration * (currentBIPS / referenceBIPS); - } - - private static double adjustExpectedDuration(long referenceDuration, double referenceBIPS) { - double currentBIPS = currentBIPS(); - System.out.println("currentBIPS=" + currentBIPS + " BIPS"); - return referenceDuration * (referenceBIPS / currentBIPS); - } -} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/BubbleSortTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/BubbleSortTest.java deleted file mode 100644 index 3592eb02..00000000 --- a/slf4j-api/src/test/java/org/slf4j/helpers/BubbleSortTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2004-2011 QOS.ch - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ -package org.slf4j.helpers; - -import java.util.Arrays; -import java.util.Random; - -import junit.framework.TestCase; - -/** - * Test that our BubbleSort algorithm is correctly implemented. - * - * @author Ceki - * - */ -public class BubbleSortTest extends TestCase { - - public void testSmoke() { - int[] a = new int[] { 5, 3, 2, 7 }; - BubbleSort.sort(a); - int i = 0; - assertEquals(2, a[i++]); - assertEquals(3, a[i++]); - assertEquals(5, a[i++]); - assertEquals(7, a[i++]); - } - - public void testEmpty() { - int[] a = new int[] {}; - BubbleSort.sort(a); - } - - public void testSorted() { - int[] a = new int[] { 3, 30, 300, 3000 }; - BubbleSort.sort(a); - int i = 0; - assertEquals(3, a[i++]); - assertEquals(30, a[i++]); - assertEquals(300, a[i++]); - assertEquals(3000, a[i++]); - } - - public void testInverted() { - int[] a = new int[] { 3000, 300, 30, 3 }; - BubbleSort.sort(a); - int i = 0; - assertEquals(3, a[i++]); - assertEquals(30, a[i++]); - assertEquals(300, a[i++]); - assertEquals(3000, a[i++]); - } - - public void testWithSameEntry() { - int[] a = new int[] { 10, 20, 10, 20 }; - BubbleSort.sort(a); - int i = 0; - assertEquals(10, a[i++]); - assertEquals(10, a[i++]); - assertEquals(20, a[i++]); - assertEquals(20, a[i++]); - } - - public void testRandom() { - int len = 100; - Random random = new Random(156); - int[] a = new int[len]; - int[] witness = new int[len]; - for (int i = 0; i < len; i++) { - int r = random.nextInt(); - a[i] = r; - witness[i] = r; - } - BubbleSort.sort(a); - Arrays.sort(witness); - assertTrue(Arrays.equals(witness, a)); - } - -} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/MDCAdapterTestBase.java b/slf4j-api/src/test/java/org/slf4j/helpers/MDCAdapterTestBase.java new file mode 100644 index 00000000..cd97b1c3 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/helpers/MDCAdapterTestBase.java @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2004-2013 QOS.ch, Copyright (C) 2015 Google Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.slf4j.helpers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Map; + +import org.junit.After; +import org.junit.Test; +import org.slf4j.spi.MDCAdapter; + +/** + * Tests for {@link BasicMDCAdapter} + * + * @author Lukasz Cwik + */ +public class MDCAdapterTestBase { + + protected MDCAdapter mdc = instantiateMDC(); + + + + protected MDCAdapter instantiateMDC() { + return new BasicMDCAdapter(); + } + + // leave MDC clean + @After + public void tearDown() throws Exception { + mdc.clear(); + } + + @Test + public void testSettingAndGettingWithMDC() { + assertNull(mdc.get("testKey")); + mdc.put("testKey", "testValue"); + assertEquals(mdc.get("testKey"), "testValue"); + } + + @Test + public void testOverwritingAKeyInMDC() { + assertNull(mdc.get("testKey")); + mdc.put("testKey", "testValue"); + mdc.put("testKey", "differentTestValue"); + assertEquals(mdc.get("testKey"), "differentTestValue"); + } + + @Test + public void testGetCopyOfContextMapFromMDC() { + mdc.put("testKey", "testValue"); + Map<String, String> copy = mdc.getCopyOfContextMap(); + mdc.put("anotherTestKey", "anotherTestValue"); + assertFalse(copy.size() == mdc.getCopyOfContextMap().size()); + } + + @Test + public void testMDCInheritsValuesFromParentThread() throws Exception { + mdc.put("parentKey", "parentValue"); + runAndWait(() -> { + mdc.put("childKey", "childValue"); + assertEquals("parentValue", mdc.get("parentKey")); + }); + } + + @Test + public void testMDCDoesntGetValuesFromChildThread() throws Exception { + mdc.put("parentKey", "parentValue"); + runAndWait(() -> mdc.put("childKey", "childValue")); + assertEquals("parentValue", mdc.get("parentKey")); + assertNull(mdc.get("childKey")); + } + + + @Test + public void testInvokingSetContextMap_WithANullMap_SLF4J_414() { + mdc.setContextMap(null); + } + + @Test + public void testMDCChildThreadCanOverwriteParentThread() throws Exception { + mdc.put("sharedKey", "parentValue"); + runAndWait(() -> { + assertEquals("parentValue", mdc.get("sharedKey")); + mdc.put("sharedKey", "childValue"); + assertEquals("childValue", mdc.get("sharedKey")); + }); + assertEquals("parentValue", mdc.get("sharedKey")); + } + + private void runAndWait(Runnable runnable) { + RecordingExceptionHandler handler = new RecordingExceptionHandler(); + Thread thread = new Thread(runnable); + thread.setUncaughtExceptionHandler(handler); + thread.start(); + try { + thread.join(); + } catch (Throwable t) { + fail("Unexpected failure in child thread:" + t.getMessage()); + } + assertFalse(handler.getMessage(), handler.hadException()); + } + + /** A {@link UncaughtExceptionHandler} that records whether the thread threw an exception. */ + private static class RecordingExceptionHandler implements UncaughtExceptionHandler { + private Throwable exception; + + @Override + public void uncaughtException(Thread t, Throwable e) { + exception = e; + } + + boolean hadException() { + return exception != null; + } + + String getMessage() { + return exception != null ? exception.getMessage() : ""; + } + } +} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/MessageFormatterPerfTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/MessageFormatterPerfTest.java deleted file mode 100755 index 7b9640bb..00000000 --- a/slf4j-api/src/test/java/org/slf4j/helpers/MessageFormatterPerfTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2004-2011 QOS.ch - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ -package org.slf4j.helpers; - -import java.text.MessageFormat; - -import junit.framework.TestCase; -import org.junit.Ignore; - -@Ignore -public class MessageFormatterPerfTest extends TestCase { - - Integer i1 = new Integer(1); - Integer i2 = new Integer(2); - static long RUN_LENGTH = 100 * 1000; - // - static long REFERENCE_BIPS = 48416; - - public MessageFormatterPerfTest(String name) { - super(name); - } - - protected void setUp() throws Exception { - } - - protected void tearDown() throws Exception { - } - - public void XtestJDKFormatterPerf() { - jdkMessageFormatter(RUN_LENGTH); - double duration = jdkMessageFormatter(RUN_LENGTH); - System.out.println("jdk duration = " + duration + " nanos"); - } - - public void testSLF4JPerf_OneArg() { - slf4jMessageFormatter_OneArg(RUN_LENGTH); - double duration = slf4jMessageFormatter_OneArg(RUN_LENGTH); - System.out.println("duration=" + duration); - long referencePerf = 36; - BogoPerf.assertDuration(duration, referencePerf, REFERENCE_BIPS); - } - - public void testSLF4JPerf_TwoArg() { - slf4jMessageFormatter_TwoArg(RUN_LENGTH); - double duration = slf4jMessageFormatter_TwoArg(RUN_LENGTH); - long referencePerf = 60; - BogoPerf.assertDuration(duration, referencePerf, REFERENCE_BIPS); - } - - public double slf4jMessageFormatter_OneArg(long len) { - long start = System.nanoTime(); - for (int i = 0; i < len; i++) { - final FormattingTuple tp = MessageFormatter.format("This is some rather short message {} ", i1); - tp.getMessage(); - tp.getArgArray(); - tp.getThrowable(); - - MessageFormatter.format("This is some rather short message {} ", i1); - } - long end = System.nanoTime(); - return (end - start) / (1000 * 1000.0); - } - - public double slf4jMessageFormatter_TwoArg(long len) { - long start = System.nanoTime(); - for (int i = 0; i < len; i++) { - final FormattingTuple tp = MessageFormatter.format("This is some {} short message {} ", i1, i2); - tp.getMessage(); - tp.getArgArray(); - tp.getThrowable(); - } - long end = System.nanoTime(); - return (end - start) / (1000 * 1000.0); - } - - public double jdkMessageFormatter(long len) { - @SuppressWarnings("unused") - String s = ""; - s += ""; // keep compiler happy - long start = System.currentTimeMillis(); - Object[] oa = new Object[] { i1 }; - for (int i = 0; i < len; i++) { - s = MessageFormat.format("This is some rather short message {0}", oa); - } - long end = System.currentTimeMillis(); - return (1.0 * end - start); - } - -} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/MessageFormatterTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/MessageFormatterTest.java index 98ebaa78..4edd59f5 100755 --- a/slf4j-api/src/test/java/org/slf4j/helpers/MessageFormatterTest.java +++ b/slf4j-api/src/test/java/org/slf4j/helpers/MessageFormatterTest.java @@ -35,11 +35,11 @@ import static org.junit.Assert.*; */ public class MessageFormatterTest { - Integer i1 = new Integer(1); - Integer i2 = new Integer(2); - Integer i3 = new Integer(3); + Integer i1 = Integer.valueOf(1); + Integer i2 = Integer.valueOf(2); + Integer i3 = Integer.valueOf(3); Integer[] ia0 = new Integer[] { i1, i2, i3 }; - Integer[] ia1 = new Integer[] { new Integer(10), new Integer(20), new Integer(30) }; + Integer[] ia1 = new Integer[] { Integer.valueOf(10), Integer.valueOf(20), Integer.valueOf(30) }; String result; @@ -50,6 +50,15 @@ public class MessageFormatterTest { } @Test + public void testParamaterContainingAnAnchor() { + result = MessageFormatter.format("Value is {}.", "[{}]").getMessage(); + assertEquals("Value is [{}].", result); + + result = MessageFormatter.format("Values are {} and {}.", i1, "[{}]").getMessage(); + assertEquals("Values are 1 and [{}].", result); + } + + @Test public void nullParametersShouldBeHandledWithoutBarfing() { result = MessageFormatter.format("Value is {}.", null).getMessage(); assertEquals("Value is null.", result); @@ -311,14 +320,20 @@ public class MessageFormatterTest { assertTrue(Arrays.equals(iaWitness, ft.getArgArray())); assertEquals(t, ft.getThrowable()); - ft = MessageFormatter.arrayFormat("Value {} is smaller than {} and {} -- {} .", ia); - assertEquals("Value 1 is smaller than 2 and 3 -- " + t.toString() + " .", ft.getMessage()); - assertTrue(Arrays.equals(ia, ft.getArgArray())); - assertNull(ft.getThrowable()); + ft = MessageFormatter.arrayFormat("Value {} is smaller than {} and {}.", ia); + assertEquals("Value 1 is smaller than 2 and 3.", ft.getMessage()); + assertTrue(Arrays.equals(iaWitness, ft.getArgArray())); + assertEquals(t, ft.getThrowable()); ft = MessageFormatter.arrayFormat("{}{}{}{}", ia); - assertEquals("123" + t.toString(), ft.getMessage()); - assertTrue(Arrays.equals(ia, ft.getArgArray())); - assertNull(ft.getThrowable()); + assertEquals("123{}", ft.getMessage()); + assertTrue(Arrays.equals(iaWitness, ft.getArgArray())); + assertEquals(t, ft.getThrowable()); + + ft = MessageFormatter.arrayFormat("1={}", new Object[] { i1 }, t); + assertEquals("1=1", ft.getMessage()); + assertTrue(Arrays.equals(new Object[] { i1 }, ft.getArgArray())); + assertEquals(t, ft.getThrowable()); + } } diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/StringPrintStream.java b/slf4j-api/src/test/java/org/slf4j/helpers/StringPrintStream.java new file mode 100755 index 00000000..68a76257 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/helpers/StringPrintStream.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2004-2021 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.helpers; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Copied from org.slfj.helpers. + * + * Currently it is not possible to use test-jar from tests running on the module-path. + * + * @author ceki + */ +public class StringPrintStream extends PrintStream { + + public static final String LINE_SEP = System.getProperty("line.separator"); + PrintStream other; + boolean duplicate = false; + + public List<String> stringList = Collections.synchronizedList(new ArrayList<>()); + + public StringPrintStream(PrintStream ps, boolean duplicate) { + super(ps); + other = ps; + this.duplicate = duplicate; + } + + public StringPrintStream(PrintStream ps) { + this(ps, false); + } + + public void print(String s) { + if (duplicate) + other.print(s); + stringList.add(s); + } + + public void println(String s) { + if (duplicate) + other.println(s); + stringList.add(s); + } + + public void println(Object o) { + if (duplicate) + other.println(o); + stringList.add(o.toString()); + } +} diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/SubstitutableLoggerTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/SubstitutableLoggerTest.java index 210252f3..f82c4f5d 100644 --- a/slf4j-api/src/test/java/org/slf4j/helpers/SubstitutableLoggerTest.java +++ b/slf4j-api/src/test/java/org/slf4j/helpers/SubstitutableLoggerTest.java @@ -24,56 +24,84 @@ */ package org.slf4j.helpers; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; -import junit.framework.TestCase; +import org.junit.Test; import org.slf4j.Logger; +import org.slf4j.event.EventRecordingLogger; /** * @author Chetan Mehrotra + * @author Ceki Gülcü */ -public class SubstitutableLoggerTest extends TestCase { - private static final Set<String> EXCLUDED_METHODS = new HashSet<String>(Arrays.asList("getName")); +public class SubstitutableLoggerTest { + + // NOTE: previous implementations of this class performed a handcrafted conversion of + // a method to a string. In this implementation we just invoke method.toString(). + + // WARNING: if you need to add an excluded method to have tests pass, ask yourself whether you + // forgot to implement the said method with delegation in SubstituteLogger. You probably did. + private static final Set<String> EXCLUDED_METHODS = new HashSet<>( + Arrays.asList("getName")); - public void testDelegate() throws Exception { - SubstituteLogger log = new SubstituteLogger("foo"); - assertTrue(log.delegate() instanceof NOPLogger); + + /** + * Test that all SubstituteLogger methods invoke the delegate, except for explicitly excluded methods. + */ + @Test + public void delegateIsInvokedTest() throws Exception { + SubstituteLogger substituteLogger = new SubstituteLogger("foo", null, false); + assertTrue(substituteLogger.delegate() instanceof EventRecordingLogger); Set<String> expectedMethodSignatures = determineMethodSignatures(Logger.class); - LoggerInvocationHandler ih = new LoggerInvocationHandler(); - Logger proxyLogger = (Logger) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Logger.class }, ih); - log.setDelegate(proxyLogger); + LoggerInvocationHandler loggerInvocationHandler = new LoggerInvocationHandler(); + Logger proxyLogger = (Logger) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Logger.class }, loggerInvocationHandler); + substituteLogger.setDelegate(proxyLogger); - invokeMethods(log); + invokeAllMethodsOf(substituteLogger); // Assert that all methods are delegated - expectedMethodSignatures.removeAll(ih.getInvokedMethodSignatures()); + expectedMethodSignatures.removeAll(loggerInvocationHandler.getInvokedMethodSignatures()); if (!expectedMethodSignatures.isEmpty()) { fail("Following methods are not delegated " + expectedMethodSignatures.toString()); } } - private void invokeMethods(Logger proxyLogger) throws InvocationTargetException, IllegalAccessException { + private void invokeAllMethodsOf(Logger logger) throws InvocationTargetException, IllegalAccessException { for (Method m : Logger.class.getDeclaredMethods()) { if (!EXCLUDED_METHODS.contains(m.getName())) { - m.invoke(proxyLogger, new Object[m.getParameterTypes().length]); + m.invoke(logger, new Object[m.getParameterTypes().length]); } } } + private static Set<String> determineMethodSignatures(Class<Logger> loggerClass) { + Set<String> methodSignatures = new HashSet<>(); + // Note: Class.getDeclaredMethods() does not include inherited methods + for (Method m : loggerClass.getDeclaredMethods()) { + if (!EXCLUDED_METHODS.contains(m.getName())) { + methodSignatures.add(m.toString()); + } + } + return methodSignatures; + } + + + // implements InvocationHandler private class LoggerInvocationHandler implements InvocationHandler { - private final Set<String> invokedMethodSignatures = new HashSet<String>(); + private final Set<String> invokedMethodSignatures = new HashSet<>(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - invokedMethodSignatures.add(getMethodSignature(method)); + invokedMethodSignatures.add(method.toString()); if (method.getName().startsWith("is")) { return true; } @@ -84,23 +112,4 @@ public class SubstitutableLoggerTest extends TestCase { return invokedMethodSignatures; } } - - private static Set<String> determineMethodSignatures(Class<Logger> loggerClass) { - Set<String> methodSignatures = new HashSet<String>(); - for (Method m : loggerClass.getDeclaredMethods()) { - if (!EXCLUDED_METHODS.contains(m.getName())) { - methodSignatures.add(getMethodSignature(m)); - } - } - return methodSignatures; - } - - private static String getMethodSignature(Method m) { - List<String> result = new ArrayList<String>(); - result.add(m.getName()); - for (Class<?> clazz : m.getParameterTypes()) { - result.add(clazz.getSimpleName()); - } - return result.toString(); - } } diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/SubstituteLoggerFactoryTest.java b/slf4j-api/src/test/java/org/slf4j/helpers/SubstituteLoggerFactoryTest.java index 370fc14b..a836f86b 100755 --- a/slf4j-api/src/test/java/org/slf4j/helpers/SubstituteLoggerFactoryTest.java +++ b/slf4j-api/src/test/java/org/slf4j/helpers/SubstituteLoggerFactoryTest.java @@ -24,16 +24,21 @@ */ package org.slf4j.helpers; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import org.slf4j.Logger; import java.util.Arrays; import java.util.HashSet; import java.util.Set; -public class SubstituteLoggerFactoryTest extends TestCase { - private SubstituteLoggerFactory factory = new SubstituteLoggerFactory(); +public class SubstituteLoggerFactoryTest { + private final SubstituteLoggerFactory factory = new SubstituteLoggerFactory(); + @Test public void testFactory() { Logger log = factory.getLogger("foo"); assertNotNull(log); @@ -42,23 +47,25 @@ public class SubstituteLoggerFactoryTest extends TestCase { assertTrue("Loggers with same name must be same", log == log2); } + @Test public void testLoggerNameList() { factory.getLogger("foo1"); factory.getLogger("foo2"); - Set<String> expectedNames = new HashSet<String>(Arrays.asList("foo1", "foo2")); - Set<String> actualNames = new HashSet<String>(factory.getLoggerNames()); + Set<String> expectedNames = new HashSet<>(Arrays.asList("foo1", "foo2")); + Set<String> actualNames = new HashSet<>(factory.getLoggerNames()); assertEquals(expectedNames, actualNames); } + @Test public void testLoggers() { factory.getLogger("foo1"); factory.getLogger("foo2"); - Set<String> expectedNames = new HashSet<String>(Arrays.asList("foo1", "foo2")); + Set<String> expectedNames = new HashSet<>(Arrays.asList("foo1", "foo2")); - Set<String> actualNames = new HashSet<String>(); + Set<String> actualNames = new HashSet<>(); for (SubstituteLogger slog : factory.getLoggers()) { actualNames.add(slog.getName()); } diff --git a/slf4j-api/src/test/java/org/slf4j/rule/BlaTest.java b/slf4j-api/src/test/java/org/slf4j/rule/BlaTest.java new file mode 100755 index 00000000..259d063a --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/rule/BlaTest.java @@ -0,0 +1,38 @@ +package org.slf4j.rule; + +import static org.junit.Assert.fail; + +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +@Ignore // this test has intentional fails +public class BlaTest { + + @Rule + public RunInNewThreadRule runInThread = new RunInNewThreadRule(); + + @Test + public void aTest() { + System.out.println("running aTest in "+ Thread.currentThread().getName()); + } + + @RunInNewThread + @Test + public void bTest() { + System.out.println("running bTest in "+ Thread.currentThread().getName()); + } + + @RunInNewThread(timeout = 2000L) + @Test + public void cTest() { + System.out.println("running cTest in "+ Thread.currentThread().getName()); + } + @RunInNewThread() + @Test + public void dTest() { + System.out.println("running dTest in "+ Thread.currentThread().getName()); + fail(); + } +} + diff --git a/slf4j-api/src/test/java/org/slf4j/helpers/BubbleSort.java b/slf4j-api/src/test/java/org/slf4j/rule/RunInNewThread.java index e8f7bcfc..1bf5944f 100644..100755 --- a/slf4j-api/src/test/java/org/slf4j/helpers/BubbleSort.java +++ b/slf4j-api/src/test/java/org/slf4j/rule/RunInNewThread.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2004-2011 QOS.ch + * Copyright (c) 2021 QOS.ch * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining @@ -22,30 +22,18 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ -package org.slf4j.helpers; -/** - * This class is used internally by BogoPerf, hence the package private - * (default) access. - * - * @author Ceki - */ -class BubbleSort { +package org.slf4j.rule; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; - static void sort(int[] a) { - int len = a.length; - for (int i = 0; i < len - 1; i++) { - for (int j = 0; j < len - 1 - i; j++) { - if (a[j] > a[j + 1]) { - swap(a, j, j + 1); - } - } - } - } - static void swap(int[] a, int i, int j) { - int t = a[i]; - a[i] = a[j]; - a[j] = t; - } +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface RunInNewThread { + static final long DEFAULT_TIMEOUT = 1000L; + public long timeout() default DEFAULT_TIMEOUT; } diff --git a/slf4j-api/src/test/java/org/slf4j/rule/RunInNewThreadRule.java b/slf4j-api/src/test/java/org/slf4j/rule/RunInNewThreadRule.java new file mode 100755 index 00000000..3945144f --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/rule/RunInNewThreadRule.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2021 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package org.slf4j.rule; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +// This class has been inspired by the article "A JUnit Rule to Run a Test in Its Own Thread" +// published by Frank Appel, author of the book "Testing with JUnit" published by Packt publishing. +// +// See also +// https://www.codeaffine.com/2014/07/21/a-junit-rule-to-run-a-test-in-its-own-thread/ + +public class RunInNewThreadRule implements TestRule { + + @Override + public Statement apply(Statement base, Description description) { + RunInNewThread desiredAnnotaton = description.getAnnotation(RunInNewThread.class); + + if (desiredAnnotaton == null) { + System.out.println("test "+ description.getMethodName() +" not annotated"); + return base; + } else { + long timeout = desiredAnnotaton.timeout(); + System.out.println("running "+ description.getMethodName() +" in separate tjread"); + return new RunInNewThreadStatement(base, timeout); + } + } + +} diff --git a/slf4j-api/src/main/java/org/slf4j/impl/StaticMDCBinder.java b/slf4j-api/src/test/java/org/slf4j/rule/RunInNewThreadStatement.java index 4fd65f40..2b8869e5 100644..100755 --- a/slf4j-api/src/main/java/org/slf4j/impl/StaticMDCBinder.java +++ b/slf4j-api/src/test/java/org/slf4j/rule/RunInNewThreadStatement.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2004-2011 QOS.ch + * Copyright (c) 2021 QOS.ch * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining @@ -22,36 +22,47 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ -package org.slf4j.impl; +package org.slf4j.rule; -import org.slf4j.spi.MDCAdapter; +import org.junit.runners.model.Statement; -/** - * This class is only a stub. Real implementations are found in - * each SLF4J binding project, e.g. slf4j-nop, slf4j-log4j12 etc. - * - * @author Ceki Gülcü - */ -public class StaticMDCBinder { +//This class has been inspired by the article "A JUnit Rule to Run a Test in Its Own Thread" +//published by Frank Appel, author of the book "Testing with JUnit" published by Packt publishing. +// +//See also +//https://www.codeaffine.com/2014/07/21/a-junit-rule-to-run-a-test-in-its-own-thread/ - /** - * The unique instance of this class. - */ - public static final StaticMDCBinder SINGLETON = new StaticMDCBinder(); +public class RunInNewThreadStatement extends Statement implements Runnable { - private StaticMDCBinder() { - throw new UnsupportedOperationException("This code should never make it into the jar"); + final Statement base; + final long timeout; + Throwable throwable; + + RunInNewThreadStatement(Statement base, long timeout) { + this.base = base; + this.timeout = timeout; } - - /** - * Currently this method always returns an instance of - * {@link StaticMDCBinder}. - */ - public MDCAdapter getMDCA() { - throw new UnsupportedOperationException("This code should never make it into the jar"); + + @Override + public void evaluate() throws Throwable { + Thread thread = new Thread(this); + thread.start(); + System.out.println("Timeout is "+timeout); + thread.join(timeout); + + if (throwable != null) { + throw throwable; + } } - public String getMDCAdapterClassStr() { - throw new UnsupportedOperationException("This code should never make it into the jar"); + @Override + public void run() { + try { + base.evaluate(); + } catch (Throwable e) { + this.throwable = e; + } } + + } diff --git a/slf4j-api/src/test/java/org/slf4j/testHarness/MultithreadedInitializationTest.java b/slf4j-api/src/test/java/org/slf4j/testHarness/MultithreadedInitializationTest.java new file mode 100644 index 00000000..c534bad1 --- /dev/null +++ b/slf4j-api/src/test/java/org/slf4j/testHarness/MultithreadedInitializationTest.java @@ -0,0 +1,79 @@ +package org.slf4j.testHarness; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerAccessingThread; +import org.slf4j.LoggerFactory; +import org.slf4j.event.EventRecordingLogger; +import org.slf4j.helpers.SubstituteLogger; + +abstract public class MultithreadedInitializationTest { + final protected static int THREAD_COUNT = 4 + Runtime.getRuntime().availableProcessors() * 2; + + private final List<Logger> createdLoggers = Collections.synchronizedList(new ArrayList<>()); + + final protected AtomicLong eventCount = new AtomicLong(0); + final private CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT + 1); + + @Test + public void multiThreadedInitialization() throws InterruptedException, BrokenBarrierException { + @SuppressWarnings("unused") + LoggerAccessingThread[] accessors = harness(); + + Logger logger = LoggerFactory.getLogger(getClass().getName()); + logger.info("hello"); + eventCount.getAndIncrement(); + + assertAllSubstLoggersAreFixed(); + long recordedEventCount = getRecordedEventCount(); + int LENIENCY_COUNT = 30; + + long expectedEventCount = eventCount.get() + extraLogEvents(); + + assertTrue(expectedEventCount + " >= " + recordedEventCount, expectedEventCount >= recordedEventCount); + assertTrue(expectedEventCount + " < " + recordedEventCount + "+" + LENIENCY_COUNT, expectedEventCount < recordedEventCount + LENIENCY_COUNT); + } + + abstract protected long getRecordedEventCount(); + + protected int extraLogEvents() { + return 0; + } + + private void assertAllSubstLoggersAreFixed() { + for (Logger logger : createdLoggers) { + if (logger instanceof SubstituteLogger) { + SubstituteLogger substLogger = (SubstituteLogger) logger; + if (substLogger.delegate() instanceof EventRecordingLogger) + fail("substLogger " + substLogger.getName() + " has a delegate of type EventRecodingLogger"); + } + } + } + + private LoggerAccessingThread[] harness() throws InterruptedException, BrokenBarrierException { + LoggerAccessingThread[] threads = new LoggerAccessingThread[THREAD_COUNT]; + for (int i = 0; i < THREAD_COUNT; i++) { + threads[i] = new LoggerAccessingThread(barrier, createdLoggers, i, eventCount); + threads[i].start(); + } + + // trigger barrier + barrier.await(); + + for (int i = 0; i < THREAD_COUNT; i++) { + threads[i].join(); + } + + return threads; + } +} |