diff options
Diffstat (limited to 'slf4j-api/src/main/java')
49 files changed, 3466 insertions, 684 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/impl/StaticMDCBinder.java b/slf4j-api/src/main/java/org/slf4j/LoggerFactoryFriend.java index 4fd65f40..06ca2017 100644..100755 --- a/slf4j-api/src/main/java/org/slf4j/impl/StaticMDCBinder.java +++ b/slf4j-api/src/main/java/org/slf4j/LoggerFactoryFriend.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2004-2011 QOS.ch + * Copyright (c) 2004-2021 QOS.ch * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining @@ -22,36 +22,34 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ -package org.slf4j.impl; - -import org.slf4j.spi.MDCAdapter; +package org.slf4j; /** - * This class is only a stub. Real implementations are found in - * each SLF4J binding project, e.g. slf4j-nop, slf4j-log4j12 etc. + * 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ü + * @author Ceki Gülcü */ -public class StaticMDCBinder { +public class LoggerFactoryFriend { - /** - * The unique instance of this class. + /* + * Force LoggerFactory to consider itself uninitialized. */ - public static final StaticMDCBinder SINGLETON = new StaticMDCBinder(); - - private StaticMDCBinder() { - throw new UnsupportedOperationException("This code should never make it into the jar"); + static public void reset() { + LoggerFactory.reset(); } /** - * Currently this method always returns an instance of - * {@link StaticMDCBinder}. + * Set LoggerFactory.DETECT_LOGGER_NAME_MISMATCH variable. + * + * @param enabled a boolean */ - public MDCAdapter getMDCA() { - throw new UnsupportedOperationException("This code should never make it into the jar"); - } - - public String getMDCAdapterClassStr() { - throw new UnsupportedOperationException("This code should never make it into the jar"); + 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(); +} |