This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch release-2.x in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 73a2cd1cd0e94c7f4f36e4ac9dc72380d30750ef Author: Gary Gregory <[email protected]> AuthorDate: Fri Jan 14 13:33:10 2022 -0500 Implement configuration APIs in and PropertyConfigurator and BasicConfigurator. - Call sites of public APIs now pick up the proper logger context based on the call sites' class loader. - Drop package private interface LoggerRepository2. - Can't break up this commit into smaller digestible chunks. --- .../java/org/apache/log4j/BasicConfigurator.java | 32 +- .../src/main/java/org/apache/log4j/Category.java | 704 ++++++++++++--------- .../src/main/java/org/apache/log4j/Hierarchy.java | 143 +++-- .../src/main/java/org/apache/log4j/LogManager.java | 105 ++- .../java/org/apache/log4j/LoggerRepository2.java | 31 - .../org/apache/log4j/PropertyConfigurator.java | 603 +++++++++++++++++- .../log4j/config/PropertiesConfiguration.java | 272 ++++---- .../org/apache/log4j/BasicConfiguratorTest.java | 56 ++ .../org/apache/log4j/PropertyConfiguratorTest.java | 427 +++++++++++++ 9 files changed, 1790 insertions(+), 583 deletions(-) diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java index da90e2c..14910d8 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java @@ -16,27 +16,45 @@ */ package org.apache.log4j; +import org.apache.logging.log4j.util.StackLocatorUtil; + /** - * Provided for compatibility with Log4j 1.x. - * + * Configures the package. + * + * <p> + * For file based configuration, see {@link PropertyConfigurator}. For XML based configuration, see + * {@link org.apache.log4j.xml.DOMConfigurator DOMConfigurator}. + * </p> + * * @since 0.8.1 */ public class BasicConfigurator { + /** + * Adds a {@link ConsoleAppender} that uses {@link PatternLayout} using the + * {@link PatternLayout#TTCC_CONVERSION_PATTERN} and prints to <code>System.out</code> to the root category. + */ public static void configure() { - LogManager.reconfigure(); + LogManager.reconfigure(StackLocatorUtil.getCallerClassLoader(2)); } /** - * No-op implementation. - * @param appender The appender. + * Adds <code>appender</code> to the root category. + * + * @param appender The appender to add to the root category. */ public static void configure(final Appender appender) { - // no-op + LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2)).addAppender(appender); } + /** + * Resets the default hierarchy to its default. It is equivalent to calling + * <code>Category.getDefaultHierarchy().resetConfiguration()</code>. + * + * See {@link Hierarchy#resetConfiguration()} for more details. + */ public static void resetConfiguration() { - LogManager.resetConfiguration(); + LogManager.resetConfiguration(StackLocatorUtil.getCallerClassLoader(2)); } /** diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java index 286ae32..90857cb 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java @@ -19,13 +19,17 @@ package org.apache.log4j; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; +import java.util.Vector; import java.util.concurrent.ConcurrentMap; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.helpers.AppenderAttachableImpl; import org.apache.log4j.helpers.NullEnumeration; import org.apache.log4j.legacy.core.CategoryUtil; import org.apache.log4j.or.ObjectRenderer; import org.apache.log4j.or.RendererMap; import org.apache.log4j.spi.AppenderAttachable; +import org.apache.log4j.spi.HierarchyEventListener; import org.apache.log4j.spi.LoggerRepository; import org.apache.log4j.spi.LoggingEvent; import org.apache.logging.log4j.message.LocalizedMessage; @@ -46,6 +50,78 @@ public class Category implements AppenderAttachable { private static final String FQCN = Category.class.getName(); /** + * Tests if the named category exists (in the default hierarchy). + * + * @param name The name to test. + * @return Whether the name exists. + * + * @deprecated Please use {@link LogManager#exists(String)} instead. + * @since 0.8.5 + */ + @Deprecated + public static Logger exists(final String name) { + return LogManager.exists(name, StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * Returns all the currently defined categories in the default hierarchy as an {@link java.util.Enumeration + * Enumeration}. + * + * <p> + * The root category is <em>not</em> included in the returned {@link Enumeration}. + * </p> + * + * @return and Enumeration of the Categories. + * + * @deprecated Please use {@link LogManager#getCurrentLoggers()} instead. + */ + @SuppressWarnings("rawtypes") + @Deprecated + public static Enumeration getCurrentCategories() { + return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * Gets the default LoggerRepository instance. + * + * @return the default LoggerRepository instance. + * @deprecated Please use {@link LogManager#getLoggerRepository()} instead. + * @since 1.0 + */ + @Deprecated + public static LoggerRepository getDefaultHierarchy() { + return LogManager.getLoggerRepository(); + } + + public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) { + return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2)); + } + + public static Category getInstance(final String name) { + return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)); + } + + public static Category getRoot() { + return LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2)); + } + + private static String getSubName(final String name) { + if (Strings.isEmpty(name)) { + return null; + } + final int i = name.lastIndexOf('.'); + return i > 0 ? name.substring(0, i) : Strings.EMPTY; + } + + /** + * Shuts down the current configuration. + */ + public static void shutdown() { + // Depth 2 gets the call site of this method. + LogManager.shutdown(StackLocatorUtil.getCallerClassLoader(2)); + } + + /** * The name of this category. */ protected String name; @@ -81,8 +157,11 @@ public class Category implements AppenderAttachable { /** Categories need to know what Hierarchy they are in. */ protected LoggerRepository repository; + AppenderAttachableImpl aai; + /** * Constructor used by Logger to specify a LoggerContext. + * * @param context The LoggerContext. * @param name The name of the Logger. */ @@ -90,158 +169,101 @@ public class Category implements AppenderAttachable { this.name = name; this.logger = context.getLogger(name); this.repository = LogManager.getLoggerRepository(); - //this.rendererMap = ((RendererSupport) repository).getRendererMap(); + // this.rendererMap = ((RendererSupport) repository).getRendererMap(); + } + + Category(final org.apache.logging.log4j.Logger logger) { + this.logger = logger; + // rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap(); } /** * Constructor exposed by Log4j 1.2. + * * @param name The name of the Logger. */ protected Category(final String name) { this(Hierarchy.getContext(), name); } - Category(final org.apache.logging.log4j.Logger logger) { - this.logger = logger; - //rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap(); - } - - public static Category getInstance(final String name) { - // Depth 2 gets the call site of this method. - return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)); - } - - public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) { - // Depth 2 gets the call site of this method. - return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2)); - } - - public final String getName() { - return logger.getName(); - } - - org.apache.logging.log4j.Logger getLogger() { - return logger; - } - - public final Category getParent() { - if (!LogManager.isLog4jCorePresent()) { - return null; + /** + * Add <code>newAppender</code> to the list of appenders of this Category instance. + * <p> + * If <code>newAppender</code> is already in the list of appenders, then it won't be added again. + * </p> + */ + @Override + public void addAppender(final Appender appender) { + if (aai == null) { + aai = new AppenderAttachableImpl(); } - final org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger); - final LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger); - if (parent == null || loggerContext == null) { - return null; + aai.addAppender(appender); + if (appender != null) { + repository.fireAddAppenderEvent(this, appender); } - final ConcurrentMap<String, Logger> loggers = Hierarchy.getLoggersMap(loggerContext); - final Logger parentLogger = loggers.get(parent.getName()); - return parentLogger == null ? new Category(parent) : parentLogger; - } - - public static Category getRoot() { - return getInstance(Strings.EMPTY); } /** - * Returns all the currently defined categories in the default hierarchy as an - * {@link java.util.Enumeration Enumeration}. + * If <code>assertion</code> parameter is {@code false}, then logs <code>msg</code> as an {@link #error(Object) error} + * statement. * * <p> - * The root category is <em>not</em> included in the returned - * {@link Enumeration}. + * The <code>assert</code> method has been renamed to <code>assertLog</code> because <code>assert</code> is a language + * reserved word in JDK 1.4. * </p> * - * @return and Enumeration of the Categories. - * - * @deprecated Please use {@link LogManager#getCurrentLoggers()} instead. - */ - @SuppressWarnings("rawtypes") - @Deprecated - public static Enumeration getCurrentCategories() { - return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2)); - } - - /** - * Gets the default LoggerRepository instance. + * @param assertion The assertion. + * @param msg The message to print if <code>assertion</code> is false. * - * @return the default LoggerRepository instance. - * @deprecated Please use {@link LogManager#getLoggerRepository()} instead. - * @since 1.0 + * @since 1.2 */ - @Deprecated - public static LoggerRepository getDefaultHierarchy() { - return LogManager.getLoggerRepository(); - } - - public Level getEffectiveLevel() { - switch (logger.getLevel().getStandardLevel()) { - case ALL: - return Level.ALL; - case TRACE: - return Level.TRACE; - case DEBUG: - return Level.DEBUG; - case INFO: - return Level.INFO; - case WARN: - return Level.WARN; - case ERROR: - return Level.ERROR; - case FATAL: - return Level.FATAL; - default: - // TODO Should this be an IllegalStateException? - return Level.OFF; + public void assertLog(final boolean assertion, final String msg) { + if (!assertion) { + this.error(msg); } } /** - * Gets the the {@link LoggerRepository} where this <code>Category</code> instance is attached. + * Call the appenders in the hierrachy starting at <code>this</code>. If no appenders could be found, emit a warning. + * <p> + * This method calls all the appenders inherited from the hierarchy circumventing any evaluation of whether to log or + * not to log the particular log request. + * </p> * - * @deprecated Please use {@link #getLoggerRepository()} instead. - * @since 1.1 + * @param event the event to log. */ - @Deprecated - public LoggerRepository getHierarchy() { - return repository; + public void callAppenders(final LoggingEvent event) { + int writes = 0; + for (Category c = this; c != null; c = c.parent) { + // Protected against simultaneous call to addAppender, removeAppender,... + synchronized (c) { + if (c.aai != null) { + writes += c.aai.appendLoopOnAppenders(event); + } + if (!c.additive) { + break; + } + } + } + if (writes == 0) { + repository.emitNoAppenderWarning(this); + } } /** - * Gets the the {@link LoggerRepository} where this <code>Category</code> is attached. + * Closes all attached appenders implementing the AppenderAttachable interface. * - * @since 1.2 + * @since 1.0 */ - public LoggerRepository getLoggerRepository() { - return repository; - } - - public Priority getChainedPriority() { - return getEffectiveLevel(); - } - - public final Level getLevel() { - return getEffectiveLevel(); - } - - private String getLevelStr(final Priority priority) { - return priority == null ? null : priority.levelStr; - } - - public void setLevel(final Level level) { - setLevel(getLevelStr(level)); - } - - public final Level getPriority() { - return getEffectiveLevel(); - } - - public void setPriority(final Priority priority) { - setLevel(getLevelStr(priority)); - } - - private void setLevel(final String levelStr) { - if (LogManager.isLog4jCorePresent()) { - CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr)); + synchronized void closeNestedAppenders() { + final Enumeration enumeration = this.getAllAppenders(); + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + final Appender a = (Appender) enumeration.nextElement(); + if (a instanceof AppenderAttachable) { + a.close(); + } + } } } @@ -253,10 +275,6 @@ public class Category implements AppenderAttachable { maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, t); } - public boolean isDebugEnabled() { - return logger.isDebugEnabled(); - } - public void error(final Object message) { maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, null); } @@ -265,22 +283,6 @@ public class Category implements AppenderAttachable { maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, t); } - public boolean isErrorEnabled() { - return logger.isErrorEnabled(); - } - - public void warn(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null); - } - - public void warn(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t); - } - - public boolean isWarnEnabled() { - return logger.isWarnEnabled(); - } - public void fatal(final Object message) { maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, null); } @@ -289,163 +291,164 @@ public class Category implements AppenderAttachable { maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, t); } - public boolean isFatalEnabled() { - return logger.isFatalEnabled(); - } - - public void info(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null); - } - - public void info(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t); - } - - public boolean isInfoEnabled() { - return logger.isInfoEnabled(); - } - - public boolean isEnabledFor(final Priority level) { - final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString()); - return isEnabledFor(lvl); - } - /** - * No-op implementation. - * @param appender The Appender to add. - */ - @Override - public void addAppender(final Appender appender) { - } - - /** - * No-op implementation. - * @param event The logging event. - */ - public void callAppenders(final LoggingEvent event) { - } - - /** - * Closes all attached appenders implementing the AppenderAttachable interface. + * LoggerRepository forgot the fireRemoveAppenderEvent method, if using the stock Hierarchy implementation, then call + * its fireRemove. Custom repositories can implement HierarchyEventListener if they want remove notifications. * - * @since 1.0 + * @param appender appender, may be null. */ - synchronized void closeNestedAppenders() { - final Enumeration enumeration = this.getAllAppenders(); - if (enumeration != null) { - while (enumeration.hasMoreElements()) { - final Appender a = (Appender) enumeration.nextElement(); - if (a instanceof AppenderAttachable) { - a.close(); - } + private void fireRemoveAppenderEvent(final Appender appender) { + if (appender != null) { + if (repository instanceof Hierarchy) { + ((Hierarchy) repository).fireRemoveAppenderEvent(this, appender); + } else if (repository instanceof HierarchyEventListener) { + ((HierarchyEventListener) repository).removeAppenderEvent(this, appender); } } } - @Override - @SuppressWarnings("rawtypes") - public Enumeration getAllAppenders() { - return NullEnumeration.getInstance(); - } - - /** - * No-op implementation. - * @param name The name of the Appender. - * @return null. - */ - @Override - public Appender getAppender(final String name) { - return null; + public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) { + final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString()); + if (logger instanceof ExtendedLogger) { + @SuppressWarnings("unchecked") + final Message msg = message instanceof Message ? (Message) message + : message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message); + ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t); + } else { + final ObjectRenderer renderer = get(message.getClass()); + final Message msg = message instanceof Message ? (Message) message + : renderer != null ? new RenderedMessage(renderer, message) : new ObjectMessage(message); + logger.log(lvl, msg, t); + } } - /** - Is the appender passed as parameter attached to this category? - * @param appender The Appender to add. - * @return true if the appender is attached. - */ - @Override - public boolean isAttached(final Appender appender) { - return false; + private <T> ObjectRenderer get(final Class<T> clazz) { + ObjectRenderer renderer = null; + for (Class<? super T> c = clazz; c != null; c = c.getSuperclass()) { + renderer = rendererMap.get(c); + if (renderer != null) { + return renderer; + } + renderer = searchInterfaces(c); + if (renderer != null) { + return renderer; + } + } + return null; } - /** - * No-op implementation. - */ - @Override - public void removeAllAppenders() { + public boolean getAdditivity() { + return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false; } /** - * No-op implementation. - * @param appender The Appender to remove. + * Get the appenders contained in this category as an {@link Enumeration}. If no appenders can be found, then a + * {@link NullEnumeration} is returned. + * + * @return Enumeration An enumeration of the appenders in this category. */ @Override - public void removeAppender(final Appender appender) { + @SuppressWarnings("rawtypes") + public Enumeration getAllAppenders() { + return aai == null ? NullEnumeration.getInstance() : aai.getAllAppenders(); } /** - * No-op implementation. - * @param name The Appender to remove. + * Look for the appender named as <code>name</code>. + * <p> + * Return the appender with that name if in the list. Return <code>null</code> otherwise. + * </p> */ @Override - public void removeAppender(final String name) { + public Appender getAppender(final String name) { + Appender appender = aai != null ? aai.getAppender(name) : null; + if (appender == null && LogManager.isLog4jCorePresent()) { + final org.apache.logging.log4j.core.Appender coreAppender = CategoryUtil.getAppenders(logger).get(name); + if (coreAppender != null) { + addAppender(appender = new AppenderWrapper(coreAppender)); + } + } + return appender; } - /** - * No-op implementation. - */ - public static void shutdown() { + public Priority getChainedPriority() { + return getEffectiveLevel(); } - public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) { - final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString()); - if (logger instanceof ExtendedLogger) { - @SuppressWarnings("unchecked") - final - Message msg = message instanceof Message ? (Message) message : message instanceof Map ? - new MapMessage((Map) message) : new ObjectMessage(message); - ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t); - } else { - final ObjectRenderer renderer = get(message.getClass()); - final Message msg = message instanceof Message ? (Message) message : renderer != null ? - new RenderedMessage(renderer, message) : new ObjectMessage(message); - logger.log(lvl, msg, t); + public Level getEffectiveLevel() { + switch (logger.getLevel().getStandardLevel()) { + case ALL: + return Level.ALL; + case TRACE: + return Level.TRACE; + case DEBUG: + return Level.DEBUG; + case INFO: + return Level.INFO; + case WARN: + return Level.WARN; + case ERROR: + return Level.ERROR; + case FATAL: + return Level.FATAL; + default: + // TODO Should this be an IllegalStateException? + return Level.OFF; } } /** - * Tests if the named category exists (in the default hierarchy). - * - * @param name The name to test. - * @return Whether the name exists. + * Gets the the {@link LoggerRepository} where this <code>Category</code> instance is attached. * - * @deprecated Please use {@link LogManager#exists(String)} instead. - * @since 0.8.5 + * @deprecated Please use {@link #getLoggerRepository()} instead. + * @since 1.1 */ @Deprecated - public static Logger exists(final String name) { - return LogManager.exists(name); + public LoggerRepository getHierarchy() { + return repository; } - public boolean getAdditivity() { - return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false; + public final Level getLevel() { + return getEffectiveLevel(); } - public void setAdditivity(final boolean additivity) { - if (LogManager.isLog4jCorePresent()) { - CategoryUtil.setAdditivity(logger, additivity); - } + private String getLevelStr(final Priority priority) { + return priority == null ? null : priority.levelStr; + } + + org.apache.logging.log4j.Logger getLogger() { + return logger; } /** - * Only the Hiearchy class can set the hiearchy of a category. Default package access is MANDATORY here. + * Gets the the {@link LoggerRepository} where this <code>Category</code> is attached. + * + * @since 1.2 */ - final void setHierarchy(final LoggerRepository repository) { - this.repository = repository; + public LoggerRepository getLoggerRepository() { + return repository; } - public void setResourceBundle(final ResourceBundle bundle) { - this.bundle = bundle; + public final String getName() { + return logger.getName(); + } + + public final Category getParent() { + if (!LogManager.isLog4jCorePresent()) { + return null; + } + final org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger); + final LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger); + if (parent == null || loggerContext == null) { + return null; + } + final ConcurrentMap<String, Logger> loggers = Hierarchy.getLoggersMap(loggerContext); + final Logger parentLogger = loggers.get(parent.getName()); + return parentLogger == null ? new Category(parent) : parentLogger; + } + + public final Level getPriority() { + return getEffectiveLevel(); } public ResourceBundle getResourceBundle() { @@ -471,39 +474,51 @@ public class Category implements AppenderAttachable { return null; } - private static String getSubName(final String name) { - if (Strings.isEmpty(name)) { - return null; - } - final int i = name.lastIndexOf('.'); - return i > 0 ? name.substring(0, i) : Strings.EMPTY; + public void info(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null); + } + + public void info(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t); } /** - * If <code>assertion</code> parameter is {@code false}, then logs - * <code>msg</code> as an {@link #error(Object) error} statement. + * Is the appender passed as parameter attached to this category? * - * <p> - * The <code>assert</code> method has been renamed to <code>assertLog</code> - * because <code>assert</code> is a language reserved word in JDK 1.4. - * </p> - * - * @param assertion The assertion. - * @param msg The message to print if <code>assertion</code> is false. - * - * @since 1.2 + * @param appender The Appender to add. + * @return true if the appender is attached. */ - public void assertLog(final boolean assertion, final String msg) { - if (!assertion) { - this.error(msg); - } + @Override + public boolean isAttached(final Appender appender) { + return aai == null ? false : aai.isAttached(appender); } - public void l7dlog(final Priority priority, final String key, final Throwable t) { - if (isEnabledFor(priority)) { - final Message msg = new LocalizedMessage(bundle, key, null); - forcedLog(FQCN, priority, msg, t); - } + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + private boolean isEnabledFor(final org.apache.logging.log4j.Level level) { + return logger.isEnabled(level); + } + + public boolean isEnabledFor(final Priority level) { + return isEnabledFor(org.apache.logging.log4j.Level.toLevel(level.toString())); + } + + public boolean isErrorEnabled() { + return logger.isErrorEnabled(); + } + + public boolean isFatalEnabled() { + return logger.isFatalEnabled(); + } + + public boolean isInfoEnabled() { + return logger.isInfoEnabled(); + } + + public boolean isWarnEnabled() { + return logger.isWarnEnabled(); } public void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) { @@ -513,10 +528,9 @@ public class Category implements AppenderAttachable { } } - public void log(final Priority priority, final Object message, final Throwable t) { + public void l7dlog(final Priority priority, final String key, final Throwable t) { if (isEnabledFor(priority)) { - @SuppressWarnings("unchecked") - final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message); + final Message msg = new LocalizedMessage(bundle, key, null); forcedLog(FQCN, priority, msg, t); } } @@ -529,6 +543,14 @@ public class Category implements AppenderAttachable { } } + public void log(final Priority priority, final Object message, final Throwable t) { + if (isEnabledFor(priority)) { + @SuppressWarnings("unchecked") + final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message); + forcedLog(FQCN, priority, msg, t); + } + } + public void log(final String fqcn, final Priority priority, final Object message, final Throwable t) { if (isEnabledFor(priority)) { final Message msg = new ObjectMessage(message); @@ -536,18 +558,13 @@ public class Category implements AppenderAttachable { } } - void maybeLog( - final String fqcn, - final org.apache.logging.log4j.Level level, - final Object message, - final Throwable throwable) { + void maybeLog(final String fqcn, final org.apache.logging.log4j.Level level, final Object message, final Throwable throwable) { if (logger.isEnabled(level)) { final Message msg; if (message instanceof String) { msg = new SimpleMessage((String) message); } - // SimpleMessage treats String and CharSequence differently, hence - // this else-if block. + // SimpleMessage treats String and CharSequence differently, hence this else-if block. else if (message instanceof CharSequence) { msg = new SimpleMessage((CharSequence) message); } else if (message instanceof Map) { @@ -565,23 +582,61 @@ public class Category implements AppenderAttachable { } } - private boolean isEnabledFor(final org.apache.logging.log4j.Level level) { - return logger.isEnabled(level); - } - - private <T> ObjectRenderer get(final Class<T> clazz) { - ObjectRenderer renderer = null; - for (Class<? super T> c = clazz; c != null; c = c.getSuperclass()) { - renderer = rendererMap.get(c); - if (renderer != null) { - return renderer; + /** + * Removes all previously added appenders from this Category instance. + * <p> + * This is useful when re-reading configuration information. + * </p> + */ + @Override + public void removeAllAppenders() { + if (aai != null) { + final Vector appenders = new Vector(); + for (final Enumeration iter = aai.getAllAppenders(); iter != null && iter.hasMoreElements();) { + appenders.add(iter.nextElement()); } - renderer = searchInterfaces(c); - if (renderer != null) { - return renderer; + aai.removeAllAppenders(); + for (final Object appender : appenders) { + fireRemoveAppenderEvent((Appender) appender); } + aai = null; + } + } + + /** + * Removes the appender passed as parameter form the list of appenders. + * + * @param appender The Appender to remove. + * @since 0.8.2 + */ + @Override + public void removeAppender(final Appender appender) { + if (appender == null || aai == null) { + return; + } + final boolean wasAttached = aai.isAttached(appender); + aai.removeAppender(appender); + if (wasAttached) { + fireRemoveAppenderEvent(appender); + } + } + + /** + * Removes the appender with the name passed as parameter form the list of appenders. + * + * @param name The Appender to remove. + * @since 0.8.2 + */ + @Override + public void removeAppender(final String name) { + if (name == null || aai == null) { + return; + } + final Appender appender = aai.getAppender(name); + aai.removeAppender(name); + if (appender != null) { + fireRemoveAppenderEvent(appender); } - return null; } ObjectRenderer searchInterfaces(final Class<?> c) { @@ -599,4 +654,43 @@ public class Category implements AppenderAttachable { return null; } + public void setAdditivity(final boolean additivity) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.setAdditivity(logger, additivity); + } + } + + /** + * Only the Hiearchy class can set the hiearchy of a category. Default package access is MANDATORY here. + */ + final void setHierarchy(final LoggerRepository repository) { + this.repository = repository; + } + + public void setLevel(final Level level) { + setLevel(getLevelStr(level)); + } + + private void setLevel(final String levelStr) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr)); + } + } + + public void setPriority(final Priority priority) { + setLevel(getLevelStr(priority)); + } + + public void setResourceBundle(final ResourceBundle bundle) { + this.bundle = bundle; + } + + public void warn(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null); + } + + public void warn(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t); + } + } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java index a7a059a..8b2f1f9 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java @@ -16,7 +16,7 @@ */ // WARNING This class MUST not have references to the Category or -// WARNING RootCategory classes in its static initiliazation neither +// WARNING RootCategory classes in its static initialization neither // WARNING directly nor indirectly. package org.apache.log4j; @@ -29,17 +29,19 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.legacy.core.ContextUtil; import org.apache.log4j.or.ObjectRenderer; import org.apache.log4j.or.RendererMap; import org.apache.log4j.spi.HierarchyEventListener; import org.apache.log4j.spi.LoggerFactory; +import org.apache.log4j.spi.LoggerRepository; import org.apache.log4j.spi.RendererSupport; import org.apache.log4j.spi.ThrowableRenderer; import org.apache.log4j.spi.ThrowableRendererSupport; import org.apache.logging.log4j.core.appender.AsyncAppender; import org.apache.logging.log4j.spi.AbstractLoggerAdapter; import org.apache.logging.log4j.spi.LoggerContext; -import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.StackLocatorUtil; /** * This class is specialized in retrieving loggers by name and also maintaining the logger hierarchy. @@ -58,7 +60,7 @@ import org.apache.logging.log4j.util.Strings; * provision node. * </p> */ -public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableRendererSupport { +public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport { private static class PrivateLoggerAdapter extends AbstractLoggerAdapter<Logger> { @@ -170,12 +172,14 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR * * <p> * You should <em>really</em> know what you are doing before invoking this method. + * </p> * * @since 0.9.0 */ public void clear() { // System.out.println("\n\nAbout to clear internal hash table."); ht.clear(); + getLoggersMap(getContext()).clear(); } @Override @@ -197,8 +201,15 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR */ @Override public Logger exists(final String name) { - final LoggerContext ctx = getContext(); - if (!ctx.hasLogger(name)) { + return exists(name, getContext()); + } + + Logger exists(final String name, final ClassLoader classLoader) { + return exists(name, getContext(classLoader)); + } + + Logger exists(final String name, final LoggerContext loggerContext) { + if (!loggerContext.hasLogger(name)) { return null; } return Logger.getLogger(name); @@ -227,6 +238,10 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR } } + LoggerContext getContext(final ClassLoader classLoader) { + return LogManager.getContext(classLoader); + } + /** * @deprecated Please use {@link #getCurrentLoggers} instead. */ @@ -241,22 +256,25 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR * * <p> * The root logger is <em>not</em> included in the returned {@link Enumeration}. + * </p> */ @Override public Enumeration getCurrentLoggers() { // The accumlation in v is necessary because not all elements in // ht are Logger objects as there might be some ProvisionNodes // as well. - final Vector v = new Vector(ht.size()); +// final Vector v = new Vector(ht.size()); +// +// final Enumeration elems = ht.elements(); +// while (elems.hasMoreElements()) { +// final Object o = elems.nextElement(); +// if (o instanceof Logger) { +// v.addElement(o); +// } +// } +// return v.elements(); - final Enumeration elems = ht.elements(); - while (elems.hasMoreElements()) { - final Object o = elems.nextElement(); - if (o instanceof Logger) { - v.addElement(o); - } - } - return v.elements(); + return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2)); } /** @@ -265,6 +283,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR * <p> * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated and * then linked with its existing ancestors as well as children. + * </p> * * @param name The name of the logger to retrieve. * @@ -274,9 +293,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR return getInstance(getContext(), name); } - @Override - public Logger getLogger(final String name, final ClassLoader classLoader) { - return getInstance(LogManager.getContext(classLoader), name); + Logger getLogger(final String name, final ClassLoader classLoader) { + return getInstance(getContext(classLoader), name); } /** @@ -285,6 +303,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR * <p> * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated by * the <code>factory</code> parameter and linked with its existing ancestors as well as children. + * </p> * * @param name The name of the logger to retrieve. * @param factory The factory that will make the new logger instance. @@ -295,9 +314,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR return getInstance(getContext(), name, factory); } - @Override - public Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) { - return getInstance(LogManager.getContext(classLoader), name, factory); + Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) { + return getInstance(getContext(classLoader), name, factory); } /** @@ -318,6 +336,10 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR return getInstance(getContext(), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); } + Logger getRootLogger(final ClassLoader classLoader) { + return getInstance(getContext(classLoader), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); + } + /** * Gets a {@link Level} representation of the <code>enable</code> state. * @@ -361,6 +383,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR * * <p> * Existing categories are not removed. They are just reset. + * </p> * * <p> * This method should be used sparingly and with care as it will block all logging until it is completed. @@ -370,6 +393,15 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR */ @Override public void resetConfiguration() { + resetConfiguration(getContext()); + } + + void resetConfiguration(final ClassLoader classLoader) { + resetConfiguration(getContext(classLoader)); + } + + void resetConfiguration(final LoggerContext loggerContext) { + getLoggersMap(loggerContext).clear(); getRootLogger().setLevel(Level.DEBUG); root.setResourceBundle(null); @@ -413,13 +445,13 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR /** * Enable logging for logging requests with level <code>l</code> or higher. By default all levels are enabled. * - * @param l The minimum level for which logging requests are sent to their appenders. + * @param level The minimum level for which logging requests are sent to their appenders. */ @Override - public void setThreshold(final Level l) { - if (l != null) { - thresholdInt = l.level; - threshold = l; + public void setThreshold(final Level level) { + if (level != null) { + thresholdInt = level.level; + threshold = level; } } @@ -428,9 +460,9 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR */ @Override public void setThreshold(final String levelStr) { - final Level l = Level.toLevel(levelStr, null); - if (l != null) { - setThreshold(l); + final Level level = Level.toLevel(levelStr, null); + if (level != null) { + setThreshold(level); } else { LogLog.warn("Could not convert [" + levelStr + "] to Level."); } @@ -440,8 +472,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR * {@inheritDoc} */ @Override - public void setThrowableRenderer(final ThrowableRenderer renderer) { - throwableRenderer = renderer; + public void setThrowableRenderer(final ThrowableRenderer throwableRenderer) { + this.throwableRenderer = throwableRenderer; } /** @@ -461,26 +493,37 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR */ @Override public void shutdown() { - final Logger root = getRootLogger(); - - // begin by closing nested appenders - root.closeNestedAppenders(); - - synchronized (ht) { - Enumeration cats = this.getCurrentLoggers(); - while (cats.hasMoreElements()) { - final Logger c = (Logger) cats.nextElement(); - c.closeNestedAppenders(); - } - - // then, remove all appenders - root.removeAllAppenders(); - cats = this.getCurrentLoggers(); - while (cats.hasMoreElements()) { - final Logger c = (Logger) cats.nextElement(); - c.removeAllAppenders(); - } - } + shutdown(getContext()); + } + + public void shutdown(final ClassLoader classLoader) { + shutdown(org.apache.logging.log4j.LogManager.getContext(classLoader, false)); + } + + void shutdown(final LoggerContext context) { +// final Logger root = getRootLogger(); +// // begin by closing nested appenders +// root.closeNestedAppenders(); +// +// synchronized (ht) { +// Enumeration cats = this.getCurrentLoggers(); +// while (cats.hasMoreElements()) { +// final Logger c = (Logger) cats.nextElement(); +// c.closeNestedAppenders(); +// } +// +// // then, remove all appenders +// root.removeAllAppenders(); +// cats = this.getCurrentLoggers(); +// while (cats.hasMoreElements()) { +// final Logger c = (Logger) cats.nextElement(); +// c.removeAllAppenders(); +// } +// } + getLoggersMap(context).clear(); + if (LogManager.isLog4jCorePresent()) { + ContextUtil.shutdown(context); + } } /** diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java index 46bff05..b18c8f1 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java @@ -69,20 +69,30 @@ public final class LogManager { LOG4J_CORE_PRESENT = checkLog4jCore(); // // By default we use a DefaultRepositorySelector which always returns 'hierarchy'. - Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG)); + final Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG)); repositorySelector = new DefaultRepositorySelector(hierarchy); } private static boolean checkLog4jCore() { try { return Class.forName("org.apache.logging.log4j.core.LoggerContext") != null; - } catch (Exception ex) { + } catch (final Throwable ex) { return false; } } + /** + * Tests if a logger for the given name exists. + * + * @param name logger name to test. + * @return whether a logger for the given name exists. + */ public static Logger exists(final String name) { - return getLoggerRepository().exists(name); + return exists(name, StackLocatorUtil.getCallerClassLoader(2)); + } + + static Logger exists(final String name, final ClassLoader classLoader) { + return getHierarchy().exists(name, classLoader); } /** @@ -98,7 +108,7 @@ public final class LogManager { /** * Gets an enumeration of the current loggers. - * + * * @return an enumeration of the current loggers. */ @SuppressWarnings("rawtypes") @@ -116,27 +126,42 @@ public final class LogManager { // @formatter:on } + static Hierarchy getHierarchy() { + final LoggerRepository loggerRepository = getLoggerRepository(); + return loggerRepository instanceof Hierarchy ? (Hierarchy) loggerRepository : null; + } + + /** + * Gets the logger for the given class. + */ public static Logger getLogger(final Class<?> clazz) { - // Depth 2 gets the call site of this method. - return getLoggerRepository2().getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2)); + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2)) + : getLoggerRepository().getLogger(clazz.getName()); } + /** + * Gets the logger for the given name. + */ public static Logger getLogger(final String name) { - // Depth 2 gets the call site of this method. - return getLoggerRepository2().getLogger(name, StackLocatorUtil.getCallerClassLoader(2)); + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)) : getLoggerRepository().getLogger(name); } static Logger getLogger(final String name, final ClassLoader classLoader) { - return getLoggerRepository2().getLogger(name, classLoader); + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, classLoader) : getLoggerRepository().getLogger(name); } public static Logger getLogger(final String name, final LoggerFactory factory) { - // Depth 2 gets the call site of this method. - return getLoggerRepository2().getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2)); + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2)) + : getLoggerRepository().getLogger(name, factory); } static Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) { - return getLoggerRepository2().getLogger(name, factory, classLoader); + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, factory, classLoader) : getLoggerRepository().getLogger(name, factory); } public static LoggerRepository getLoggerRepository() { @@ -146,48 +171,62 @@ public final class LogManager { return repositorySelector.getLoggerRepository(); } - static LoggerRepository2 getLoggerRepository2() { - // TODO Hack - return (LoggerRepository2) getLoggerRepository(); + /** + * Gets the root logger. + */ + public static Logger getRootLogger() { + return getRootLogger(StackLocatorUtil.getCallerClassLoader(2)); } - public static Logger getRootLogger() { - return getLoggerRepository2().getRootLogger(); + static Logger getRootLogger(final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getRootLogger(classLoader) : getLoggerRepository().getRootLogger(); } static boolean isLog4jCorePresent() { return LOG4J_CORE_PRESENT; } - static void reconfigure() { + static void reconfigure(final ClassLoader classLoader) { if (isLog4jCorePresent()) { - ContextUtil.reconfigure(Hierarchy.getContext()); + ContextUtil.reconfigure(LogManager.getContext(classLoader)); } } - /** - * No-op implementation. - */ public static void resetConfiguration() { - // noop + resetConfiguration(StackLocatorUtil.getCallerClassLoader(2)); + } + + static void resetConfiguration(final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + if (hierarchy != null) { + hierarchy.resetConfiguration(classLoader); + } else { + getLoggerRepository().resetConfiguration(); + } } - /** - * No-op implementation. - * - * @param selector The RepositorySelector. - * @param guard prevents calls at the incorrect time. - * @throws IllegalArgumentException if a parameter is invalid. - */ public static void setRepositorySelector(final RepositorySelector selector, final Object guard) throws IllegalArgumentException { - // noop + if (selector == null) { + throw new IllegalArgumentException("RepositorySelector must be non-null."); + } + LogManager.repositorySelector = selector; } /** - * No-op implementation. + * Shuts down the current configuration. */ public static void shutdown() { - // noop + shutdown(StackLocatorUtil.getCallerClassLoader(2)); + } + + static void shutdown(final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + if (hierarchy != null) { + hierarchy.shutdown(classLoader); + } else { + getLoggerRepository().shutdown(); + } } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java deleted file mode 100644 index 30c88ee..0000000 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.log4j; - -import org.apache.log4j.spi.LoggerFactory; -import org.apache.log4j.spi.LoggerRepository; - -/** - * A LoggerRepository that accounts for the caller's class loader. - */ -interface LoggerRepository2 extends LoggerRepository { - - Logger getLogger(String name, ClassLoader classLoader); - - Logger getLogger(String name, LoggerFactory factory, ClassLoader classLoader); -} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java index 808220a..45a96d4 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java @@ -16,114 +16,657 @@ */ package org.apache.log4j; +import java.io.IOException; import java.io.InputStream; +import java.io.InterruptedIOException; import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Map; import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.config.PropertySetter; +import org.apache.log4j.helpers.FileWatchdog; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.or.RendererMap; import org.apache.log4j.spi.Configurator; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggerFactory; import org.apache.log4j.spi.LoggerRepository; +import org.apache.log4j.spi.OptionHandler; +import org.apache.log4j.spi.RendererSupport; +import org.apache.log4j.spi.ThrowableRenderer; +import org.apache.log4j.spi.ThrowableRendererSupport; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.util.StackLocatorUtil; /** - * A configurator for properties. + * Configures Log4j from properties. */ public class PropertyConfigurator implements Configurator { + static class NameValue { + String key, value; + + public NameValue(final String key, final String value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return key + "=" + value; + } + } + + static class PropertyWatchdog extends FileWatchdog { + + private final ClassLoader classLoader; + + PropertyWatchdog(final String fileName, final ClassLoader classLoader) { + super(fileName); + this.classLoader = classLoader; + } + + /** + * Call {@link PropertyConfigurator#configure(String)} with the <code>filename</code> to reconfigure log4j. + */ + @Override + public void doOnChange() { + new PropertyConfigurator().doConfigure(filename, LogManager.getLoggerRepository(), classLoader); + } + } + + class SortedKeyEnumeration implements Enumeration { + + private final Enumeration e; + + public SortedKeyEnumeration(final Hashtable ht) { + final Enumeration f = ht.keys(); + final Vector keys = new Vector(ht.size()); + for (int i, last = 0; f.hasMoreElements(); ++last) { + final String key = (String) f.nextElement(); + for (i = 0; i < last; ++i) { + final String s = (String) keys.get(i); + if (key.compareTo(s) <= 0) { + break; + } + } + keys.add(i, key); + } + e = keys.elements(); + } + + @Override + public boolean hasMoreElements() { + return e.hasMoreElements(); + } + + @Override + public Object nextElement() { + return e.nextElement(); + } + } + + static final String CATEGORY_PREFIX = "log4j.category."; + static final String LOGGER_PREFIX = "log4j.logger."; + static final String FACTORY_PREFIX = "log4j.factory"; + static final String ADDITIVITY_PREFIX = "log4j.additivity."; + static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory"; + static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger"; + static final String APPENDER_PREFIX = "log4j.appender."; + static final String RENDERER_PREFIX = "log4j.renderer."; + static final String THRESHOLD_PREFIX = "log4j.threshold"; + + private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer"; + private static final String LOGGER_REF = "logger-ref"; + private static final String ROOT_REF = "root-ref"; + private static final String APPENDER_REF_TAG = "appender-ref"; + + /** + * Key for specifying the {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}. Currently set to + * "<code>log4j.loggerFactory</code>". + */ + public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory"; + + /** + * If property set to true, then hierarchy will be reset before configuration. + */ + private static final String RESET_KEY = "log4j.reset"; + + static final private String INTERNAL_ROOT_NAME = "root"; + /** * Reads configuration options from an InputStream. * * @param inputStream The input stream */ public static void configure(final InputStream inputStream) { + new PropertyConfigurator().doConfigure(inputStream, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from <code>properties</code>. + * Reads configuration options from <code>properties</code>. * * See {@link #doConfigure(String, LoggerRepository)} for the expected format. * * @param properties The properties */ public static void configure(final Properties properties) { + new PropertyConfigurator().doConfigure(properties, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from configuration file. + * Reads configuration options from configuration file. * - * @param configFileName The configuration file. + * @param fileName The configuration file. */ - public static void configure(final String configFileName) { + public static void configure(final String fileName) { + new PropertyConfigurator().doConfigure(fileName, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from url <code>configURL</code>. + * Reads configuration options from url <code>configURL</code>. * * @param configURL The configuration URL */ public static void configure(final URL configURL) { + new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Like {@link #configureAndWatch(String, long)} except that the - * default delay as defined by FileWatchdog.DEFAULT_DELAY is - * used. + * Like {@link #configureAndWatch(String, long)} except that the default delay as defined by FileWatchdog.DEFAULT_DELAY + * is used. * * @param configFilename A file in key=value format. */ public static void configureAndWatch(final String configFilename) { + configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY, StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read the configuration file <code>configFilename</code> if it - * exists. Moreover, a thread will be created that will periodically - * check if <code>configFilename</code> has been created or - * modified. The period is determined by the <code>delay</code> - * argument. If a change or file creation is detected, then - * <code>configFilename</code> is read to configure log4j. + * Reads the configuration file <code>configFilename</code> if it exists. Moreover, a thread will be created that will + * periodically check if <code>configFilename</code> has been created or modified. The period is determined by the + * <code>delay</code> argument. If a change or file creation is detected, then <code>configFilename</code> is read to + * configure log4j. * * @param configFilename A file in key=value format. - * @param delay The delay in milliseconds to wait between each check. + * @param delayMillis The delay in milliseconds to wait between each check. */ - public static void configureAndWatch(final String configFilename, final long delay) { + public static void configureAndWatch(final String configFilename, final long delayMillis) { + configureAndWatch(configFilename, delayMillis, StackLocatorUtil.getCallerClassLoader(2)); + } + + static void configureAndWatch(final String configFilename, final long delay, final ClassLoader classLoader) { + final PropertyWatchdog watchdog = new PropertyWatchdog(configFilename, classLoader); + watchdog.setDelay(delay); + watchdog.start(); + } + + private static Configuration reconfigure(final Configuration configuration) { + org.apache.logging.log4j.core.config.Configurator.reconfigure(configuration); + return configuration; } /** - * Read configuration options from an InputStream. + * Used internally to keep track of configured appenders. + */ + protected Hashtable registry = new Hashtable(11); + + private LoggerRepository repository; + + protected LoggerFactory loggerFactory = new DefaultCategoryFactory(); + + /** + * Checks the provided <code>Properties</code> object for a {@link org.apache.log4j.spi.LoggerFactory LoggerFactory} + * entry specified by {@link #LOGGER_FACTORY_KEY}. If such an entry exists, an attempt is made to create an instance + * using the default constructor. This instance is used for subsequent Category creations within this configurator. + * + * @see #parseCatsAndRenderers + */ + protected void configureLoggerFactory(final Properties properties) { + final String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, properties); + if (factoryClassName != null) { + LogLog.debug("Setting category factory to [" + factoryClassName + "]."); + loggerFactory = (LoggerFactory) OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory); + PropertySetter.setProperties(loggerFactory, properties, FACTORY_PREFIX + "."); + } + } + + void configureRootCategory(final Properties properties, final LoggerRepository loggerRepository) { + String effectiveFrefix = ROOT_LOGGER_PREFIX; + String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, properties); + + if (value == null) { + value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, properties); + effectiveFrefix = ROOT_CATEGORY_PREFIX; + } + + if (value == null) { + LogLog.debug("Could not find root logger information. Is this OK?"); + } else { + final Logger root = loggerRepository.getRootLogger(); + synchronized (root) { + parseCategory(properties, root, effectiveFrefix, INTERNAL_ROOT_NAME, value); + } + } + } + + /** + * Reads configuration options from an InputStream. * * @param inputStream The input stream - * @param hierarchy The hierarchy + * @param loggerRepository The hierarchy */ @Override - public void doConfigure(final InputStream inputStream, final LoggerRepository hierarchy) { + public void doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository) { + doConfigure(inputStream, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); + } + + Configuration doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + return doConfigure(loadProperties(inputStream), loggerRepository, classLoader); } /** - * Read configuration options from <code>properties</code>. + * Reads configuration options from <code>properties</code>. * * See {@link #doConfigure(String, LoggerRepository)} for the expected format. * * @param properties The properties - * @param hierarchy The hierarchy + * @param loggerRepository The hierarchy */ - public void doConfigure(final Properties properties, final LoggerRepository hierarchy) { + public void doConfigure(final Properties properties, final LoggerRepository loggerRepository) { + doConfigure(properties, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from configuration file. + * Reads configuration options from <code>properties</code>. + * + * See {@link #doConfigure(String, LoggerRepository)} for the expected format. * - * @param configFileName The configuration file - * @param hierarchy The hierarchy + * @param properties The properties + * @param loggerRepository The hierarchy */ - public void doConfigure(final String configFileName, final LoggerRepository hierarchy) { + Configuration doConfigure(final Properties properties, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + final PropertiesConfiguration configuration = new PropertiesConfiguration(LogManager.getContext(classLoader), properties); + configuration.doConfigure(); + + repository = loggerRepository; +// String value = properties.getProperty(LogLog.DEBUG_KEY); +// if (value == null) { +// value = properties.getProperty("log4j.configDebug"); +// if (value != null) { +// LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); +// } +// } +// +// if (value != null) { +// LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); +// } +// +// // +// // if log4j.reset=true then +// // reset hierarchy +// final String reset = properties.getProperty(RESET_KEY); +// if (reset != null && OptionConverter.toBoolean(reset, false)) { +// hierarchy.resetConfiguration(); +// } +// +// final String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties); +// if (thresholdStr != null) { +// hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL)); +// LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "]."); +// } +// +// configureRootCategory(properties, hierarchy); +// configureLoggerFactory(properties); +// parseCatsAndRenderers(properties, hierarchy); +// + // We don't want to hold references to appenders preventing their + // garbage collection. + registry.clear(); + return reconfigure(configuration); + } + + /** + * Reads configuration options from configuration file. + * + * @param fileName The configuration file + * @param loggerRepository The hierarchy + */ + public void doConfigure(final String fileName, final LoggerRepository loggerRepository) { + doConfigure(fileName, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * Reads configuration options from configuration file. + * + * @param fileName The configuration file + * @param loggerRepository The hierarchy + */ + Configuration doConfigure(final String fileName, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + try (InputStream inputStream = Files.newInputStream(Paths.get(fileName))) { + return doConfigure(inputStream, loggerRepository, classLoader); + } catch (final Exception e) { + if (e instanceof InterruptedIOException || e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + LogLog.error("Could not read configuration file [" + fileName + "].", e); + LogLog.error("Ignoring configuration file [" + fileName + "]."); + return null; + } } /** * Read configuration options from url <code>configURL</code>. * - * @param configURL The configuration URL - * @param hierarchy The hierarchy + * @param url The configuration URL + * @param loggerRepository The hierarchy */ @Override - public void doConfigure(final URL configURL, final LoggerRepository hierarchy) { + public void doConfigure(final URL url, final LoggerRepository loggerRepository) { + doConfigure(url, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); + } + + Configuration doConfigure(final URL url, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + LogLog.debug("Reading configuration from URL " + url); + try { + final URLConnection urlConnection = url.openConnection(); + urlConnection.setUseCaches(false); + try (InputStream inputStream = urlConnection.getInputStream()) { + return doConfigure(inputStream, loggerRepository, classLoader); + } + } catch (final IOException e) { + LogLog.error("Could not read configuration file from URL [" + url + "].", e); + LogLog.error("Ignoring configuration file [" + url + "]."); + return null; + } + + } + + private Properties loadProperties(final InputStream inputStream) { + final Properties loaded = new Properties(); + try { + loaded.load(inputStream); + } catch (final IOException | IllegalArgumentException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.error("Could not read configuration file from InputStream [" + inputStream + "].", e); + LogLog.error("Ignoring configuration InputStream [" + inputStream + "]."); + return null; + } + return loaded; + } + + /** + * Parse the additivity option for a non-root category. + */ + void parseAdditivityForLogger(final Properties properties, final Logger logger, final String loggerName) { + final String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, properties); + LogLog.debug("Handling " + ADDITIVITY_PREFIX + loggerName + "=[" + value + "]"); + // touch additivity only if necessary + if ((value != null) && (!value.equals(""))) { + final boolean additivity = OptionConverter.toBoolean(value, true); + LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity); + logger.setAdditivity(additivity); + } + } + + Appender parseAppender(final Properties properties, final String appenderName) { + Appender appender = registryGet(appenderName); + if ((appender != null)) { + LogLog.debug("Appender \"" + appenderName + "\" was already parsed."); + return appender; + } + // Appender was not previously initialized. + final String prefix = APPENDER_PREFIX + appenderName; + final String layoutPrefix = prefix + ".layout"; + + appender = (Appender) OptionConverter.instantiateByKey(properties, prefix, org.apache.log4j.Appender.class, null); + if (appender == null) { + LogLog.error("Could not instantiate appender named \"" + appenderName + "\"."); + return null; + } + appender.setName(appenderName); + + if (appender instanceof OptionHandler) { + if (appender.requiresLayout()) { + final Layout layout = (Layout) OptionConverter.instantiateByKey(properties, layoutPrefix, Layout.class, null); + if (layout != null) { + appender.setLayout(layout); + LogLog.debug("Parsing layout options for \"" + appenderName + "\"."); + // configureOptionHandler(layout, layoutPrefix + ".", props); + PropertySetter.setProperties(layout, properties, layoutPrefix + "."); + LogLog.debug("End of parsing for \"" + appenderName + "\"."); + } + } + final String errorHandlerPrefix = prefix + ".errorhandler"; + final String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, properties); + if (errorHandlerClass != null) { + final ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(properties, errorHandlerPrefix, ErrorHandler.class, null); + if (eh != null) { + appender.setErrorHandler(eh); + LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\"."); + parseErrorHandler(eh, errorHandlerPrefix, properties, repository); + final Properties edited = new Properties(); + final String[] keys = new String[] {errorHandlerPrefix + "." + ROOT_REF, errorHandlerPrefix + "." + LOGGER_REF, + errorHandlerPrefix + "." + APPENDER_REF_TAG}; + for (final Object element : properties.entrySet()) { + final Map.Entry entry = (Map.Entry) element; + int i = 0; + for (; i < keys.length; i++) { + if (keys[i].equals(entry.getKey())) { + break; + } + } + if (i == keys.length) { + edited.put(entry.getKey(), entry.getValue()); + } + } + PropertySetter.setProperties(eh, edited, errorHandlerPrefix + "."); + LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\"."); + } + + } + // configureOptionHandler((OptionHandler) appender, prefix + ".", props); + PropertySetter.setProperties(appender, properties, prefix + "."); + LogLog.debug("Parsed \"" + appenderName + "\" options."); + } + parseAppenderFilters(properties, appenderName, appender); + registryPut(appender); + return appender; + } + + void parseAppenderFilters(final Properties properties, final String appenderName, final Appender appender) { + // extract filters and filter options from props into a hashtable mapping + // the property name defining the filter class to a list of pre-parsed + // name-value pairs associated to that filter + final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter."; + final int fIdx = filterPrefix.length(); + final Hashtable filters = new Hashtable(); + final Enumeration e = properties.keys(); + String name = ""; + while (e.hasMoreElements()) { + final String key = (String) e.nextElement(); + if (key.startsWith(filterPrefix)) { + final int dotIdx = key.indexOf('.', fIdx); + String filterKey = key; + if (dotIdx != -1) { + filterKey = key.substring(0, dotIdx); + name = key.substring(dotIdx + 1); + } + Vector filterOpts = (Vector) filters.get(filterKey); + if (filterOpts == null) { + filterOpts = new Vector(); + filters.put(filterKey, filterOpts); + } + if (dotIdx != -1) { + final String value = OptionConverter.findAndSubst(key, properties); + filterOpts.add(new NameValue(name, value)); + } + } + } + + // sort filters by IDs, insantiate filters, set filter options, + // add filters to the appender + final Enumeration g = new SortedKeyEnumeration(filters); + while (g.hasMoreElements()) { + final String key = (String) g.nextElement(); + final String clazz = properties.getProperty(key); + if (clazz != null) { + LogLog.debug("Filter key: [" + key + "] class: [" + properties.getProperty(key) + "] props: " + filters.get(key)); + final Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null); + if (filter != null) { + final PropertySetter propSetter = new PropertySetter(filter); + final Vector v = (Vector) filters.get(key); + final Enumeration filterProps = v.elements(); + while (filterProps.hasMoreElements()) { + final NameValue kv = (NameValue) filterProps.nextElement(); + propSetter.setProperty(kv.key, kv.value); + } + propSetter.activate(); + LogLog.debug("Adding filter of type [" + filter.getClass() + "] to appender named [" + appender.getName() + "]."); + appender.addFilter(filter); + } + } else { + LogLog.warn("Missing class definition for filter: [" + key + "]"); + } + } + } + + /** + * This method must work for the root category as well. + */ + void parseCategory(final Properties properties, final Logger logger, final String optionKey, final String loggerName, final String value) { + + LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "]."); + // We must skip over ',' but not white space + final StringTokenizer st = new StringTokenizer(value, ","); + + // If value is not in the form ", appender.." or "", then we should set + // the level of the loggeregory. + + if (!(value.startsWith(",") || value.equals(""))) { + + // just to be on the safe side... + if (!st.hasMoreTokens()) { + return; + } + + final String levelStr = st.nextToken(); + LogLog.debug("Level token is [" + levelStr + "]."); + + // If the level value is inherited, set category level value to + // null. We also check that the user has not specified inherited for the + // root category. + if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) { + if (loggerName.equals(INTERNAL_ROOT_NAME)) { + LogLog.warn("The root logger cannot be set to null."); + } else { + logger.setLevel(null); + } + } else { + logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG)); + } + LogLog.debug("Category " + loggerName + " set to " + logger.getLevel()); + } + + // Begin by removing all existing appenders. + logger.removeAllAppenders(); + + Appender appender; + String appenderName; + while (st.hasMoreTokens()) { + appenderName = st.nextToken().trim(); + if (appenderName == null || appenderName.equals(",")) { + continue; + } + LogLog.debug("Parsing appender named \"" + appenderName + "\"."); + appender = parseAppender(properties, appenderName); + if (appender != null) { + logger.addAppender(appender); + } + } + } + + /** + * Parse non-root elements, such non-root categories and renderers. + */ + protected void parseCatsAndRenderers(final Properties properties, final LoggerRepository loggerRepository) { + final Enumeration enumeration = properties.propertyNames(); + while (enumeration.hasMoreElements()) { + final String key = (String) enumeration.nextElement(); + if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) { + String loggerName = null; + if (key.startsWith(CATEGORY_PREFIX)) { + loggerName = key.substring(CATEGORY_PREFIX.length()); + } else if (key.startsWith(LOGGER_PREFIX)) { + loggerName = key.substring(LOGGER_PREFIX.length()); + } + final String value = OptionConverter.findAndSubst(key, properties); + final Logger logger = loggerRepository.getLogger(loggerName, loggerFactory); + synchronized (logger) { + parseCategory(properties, logger, key, loggerName, value); + parseAdditivityForLogger(properties, logger, loggerName); + } + } else if (key.startsWith(RENDERER_PREFIX)) { + final String renderedClass = key.substring(RENDERER_PREFIX.length()); + final String renderingClass = OptionConverter.findAndSubst(key, properties); + if (loggerRepository instanceof RendererSupport) { + RendererMap.addRenderer((RendererSupport) loggerRepository, renderedClass, renderingClass); + } + } else if (key.equals(THROWABLE_RENDERER_PREFIX)) { + if (loggerRepository instanceof ThrowableRendererSupport) { + final ThrowableRenderer tr = (ThrowableRenderer) OptionConverter.instantiateByKey(properties, THROWABLE_RENDERER_PREFIX, + org.apache.log4j.spi.ThrowableRenderer.class, null); + if (tr == null) { + LogLog.error("Could not instantiate throwableRenderer."); + } else { + final PropertySetter setter = new PropertySetter(tr); + setter.setProperties(properties, THROWABLE_RENDERER_PREFIX + "."); + ((ThrowableRendererSupport) loggerRepository).setThrowableRenderer(tr); + + } + } + } + } + } + + private void parseErrorHandler(final ErrorHandler errorHandler, final String errorHandlerPrefix, final Properties props, + final LoggerRepository loggerRepository) { + final boolean rootRef = OptionConverter.toBoolean(OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false); + if (rootRef) { + errorHandler.setLogger(loggerRepository.getRootLogger()); + } + final String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF, props); + if (loggerName != null) { + final Logger logger = (loggerFactory == null) ? loggerRepository.getLogger(loggerName) : loggerRepository.getLogger(loggerName, loggerFactory); + errorHandler.setLogger(logger); + } + final String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props); + if (appenderName != null) { + final Appender backup = parseAppender(props, appenderName); + if (backup != null) { + errorHandler.setBackupAppender(backup); + } + } + } + + Appender registryGet(final String name) { + return (Appender) registry.get(name); + } + + void registryPut(final Appender appender) { + registry.put(appender.getName(), appender); } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java index 8be58d4..a8d8614 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java @@ -56,15 +56,21 @@ public class PropertiesConfiguration extends Log4j1Configuration { private static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory"; private static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger"; private static final String APPENDER_PREFIX = "log4j.appender."; - private static final String LOGGER_REF = "logger-ref"; - private static final String ROOT_REF = "root-ref"; + private static final String LOGGER_REF = "logger-ref"; + private static final String ROOT_REF = "root-ref"; private static final String APPENDER_REF_TAG = "appender-ref"; - public static final long DEFAULT_DELAY = 60000; - public static final String DEBUG_KEY="log4j.debug"; + + /** + * If property set to true, then hierarchy will be reset before configuration. + */ + private static final String RESET_KEY = "log4j.reset"; + + public static final String DEBUG_KEY = "log4j.debug"; private static final String INTERNAL_ROOT_NAME = "root"; private final Map<String, Appender> registry = new HashMap<>(); + private Properties properties; /** * Constructs a new instance. @@ -73,20 +79,44 @@ public class PropertiesConfiguration extends Log4j1Configuration { * @param source The ConfigurationSource. * @param monitorIntervalSeconds The monitoring interval in seconds. */ - public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, - final int monitorIntervalSeconds) { + public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, final int monitorIntervalSeconds) { super(loggerContext, source, monitorIntervalSeconds); } + /** + * Constructs a new instance. + * + * @param loggerContext The LoggerContext. + * @param properties The ConfigurationSource, may be null. + */ + public PropertiesConfiguration(final LoggerContext loggerContext, final Properties properties) { + super(loggerContext, ConfigurationSource.NULL_SOURCE, 0); + this.properties = properties; + } + + /** + * Constructs a new instance. + * + * @param loggerContext The LoggerContext. + * @param properties The ConfigurationSource. + */ + public PropertiesConfiguration(org.apache.logging.log4j.spi.LoggerContext loggerContext, Properties properties) { + this((LoggerContext) loggerContext, properties); + } + @Override public void doConfigure() { - final InputStream inputStream = getConfigurationSource().getInputStream(); - final Properties properties = new Properties(); - try { - properties.load(inputStream); - } catch (final Exception e) { - LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e); - return; + if (properties == null) { + properties = new Properties(); + final InputStream inputStream = getConfigurationSource().getInputStream(); + if (inputStream != null) { + try { + properties.load(inputStream); + } catch (final Exception e) { + LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e); + return; + } + } } // If we reach here, then the config file is alright. doConfigure(properties); @@ -108,27 +138,26 @@ public class PropertiesConfiguration extends Log4j1Configuration { } /** - * Reads a configuration from a file. <b>The existing configuration is - * not cleared nor reset.</b> If you require a different behavior, - * then call {@link LogManager#resetConfiguration - * resetConfiguration} method before calling + * Reads a configuration from a file. <b>The existing configuration is not cleared nor reset.</b> If you require a + * different behavior, then call {@link LogManager#resetConfiguration resetConfiguration} method before calling * <code>doConfigure</code>. * - * <p>The configuration file consists of statements in the format - * <code>key=value</code>. The syntax of different configuration - * elements are discussed below. + * <p> + * The configuration file consists of statements in the format <code>key=value</code>. The syntax of different + * configuration elements are discussed below. * - * <p>The level value can consist of the string values OFF, FATAL, - * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A - * custom level value can be specified in the form - * level#classname. By default the repository-wide threshold is set - * to the lowest possible value, namely the level <code>ALL</code>. + * <p> + * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> + * value. A custom level value can be specified in the form level#classname. By default the repository-wide threshold is + * set to the lowest possible value, namely the level <code>ALL</code>. * </p> * * * <h3>Appender configuration</h3> * - * <p>Appender configuration syntax is: + * <p> + * Appender configuration syntax is: + * * <pre> * # For appender named <i>appenderName</i>, set its class. * # Note: The appender name can contain dots. @@ -140,8 +169,8 @@ public class PropertiesConfiguration extends Log4j1Configuration { * log4j.appender.appenderName.optionN=valueN * </pre> * <p> - * For each named appender you can configure its {@link Layout}. The - * syntax for configuring an appender's layout is: + * For each named appender you can configure its {@link Layout}. The syntax for configuring an appender's layout is: + * * <pre> * log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class * log4j.appender.appenderName.layout.option1=value1 @@ -150,18 +179,19 @@ public class PropertiesConfiguration extends Log4j1Configuration { * </pre> * <p> * The syntax for adding {@link Filter}s to an appender is: + * * <pre> * log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class * log4j.appender.appenderName.filter.ID.option1=value1 * ... * log4j.appender.appenderName.filter.ID.optionN=valueN * </pre> - * The first line defines the class name of the filter identified by ID; - * subsequent lines with the same ID specify filter option - value - * pairs. Multiple filters are added to the appender in the lexicographic - * order of IDs. + * + * The first line defines the class name of the filter identified by ID; subsequent lines with the same ID specify + * filter option - value pairs. Multiple filters are added to the appender in the lexicographic order of IDs. * <p> * The syntax for adding an {@link ErrorHandler} to an appender is: + * * <pre> * log4j.appender.appenderName.errorhandler=fully.qualified.name.of.errorhandler.class * log4j.appender.appenderName.errorhandler.appender-ref=appenderName @@ -172,111 +202,97 @@ public class PropertiesConfiguration extends Log4j1Configuration { * * <h3>Configuring loggers</h3> * - * <p>The syntax for configuring the root logger is: + * <p> + * The syntax for configuring the root logger is: + * * <pre> * log4j.rootLogger=[level], appenderName, appenderName, ... * </pre> * - * <p>This syntax means that an optional <em>level</em> can be - * supplied followed by appender names separated by commas. + * <p> + * This syntax means that an optional <em>level</em> can be supplied followed by appender names separated by commas. * - * <p>The level value can consist of the string values OFF, FATAL, - * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A - * custom level value can be specified in the form - * <code>level#classname</code>. + * <p> + * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> + * value. A custom level value can be specified in the form <code>level#classname</code>. * - * <p>If a level value is specified, then the root level is set - * to the corresponding level. If no level value is specified, + * <p> + * If a level value is specified, then the root level is set to the corresponding level. If no level value is specified, * then the root level remains untouched. * - * <p>The root logger can be assigned multiple appenders. + * <p> + * The root logger can be assigned multiple appenders. * - * <p>Each <i>appenderName</i> (separated by commas) will be added to - * the root logger. The named appender is defined using the - * appender syntax defined above. + * <p> + * Each <i>appenderName</i> (separated by commas) will be added to the root logger. The named appender is defined using + * the appender syntax defined above. * - * <p>For non-root categories the syntax is almost the same: + * <p> + * For non-root categories the syntax is almost the same: + * * <pre> * log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ... * </pre> * - * <p>The meaning of the optional level value is discussed above - * in relation to the root logger. In addition however, the value - * INHERITED can be specified meaning that the named logger should - * inherit its level from the logger hierarchy. + * <p> + * The meaning of the optional level value is discussed above in relation to the root logger. In addition however, the + * value INHERITED can be specified meaning that the named logger should inherit its level from the logger hierarchy. * - * <p>If no level value is supplied, then the level of the - * named logger remains untouched. + * <p> + * If no level value is supplied, then the level of the named logger remains untouched. * - * <p>By default categories inherit their level from the - * hierarchy. However, if you set the level of a logger and later - * decide that that logger should inherit its level, then you should - * specify INHERITED as the value for the level value. NULL is a - * synonym for INHERITED. + * <p> + * By default categories inherit their level from the hierarchy. However, if you set the level of a logger and later + * decide that that logger should inherit its level, then you should specify INHERITED as the value for the level value. + * NULL is a synonym for INHERITED. * - * <p>Similar to the root logger syntax, each <i>appenderName</i> - * (separated by commas) will be attached to the named logger. + * <p> + * Similar to the root logger syntax, each <i>appenderName</i> (separated by commas) will be attached to the named + * logger. * - * <p>See the <a href="../../../../manual.html#additivity">appender - * additivity rule</a> in the user manual for the meaning of the - * <code>additivity</code> flag. + * <p> + * See the <a href="../../../../manual.html#additivity">appender additivity rule</a> in the user manual for the meaning + * of the <code>additivity</code> flag. * * - * # Set options for appender named "A1". - * # Appender "A1" will be a SyslogAppender + * # Set options for appender named "A1". # Appender "A1" will be a SyslogAppender * log4j.appender.A1=org.apache.log4j.net.SyslogAppender * - * # The syslog daemon resides on www.abc.net - * log4j.appender.A1.SyslogHost=www.abc.net - * - * # A1's layout is a PatternLayout, using the conversion pattern - * # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will - * # include # the relative time since the start of the application in - * # milliseconds, followed by the level of the log request, - * # followed by the two rightmost components of the logger name, - * # followed by the callers method name, followed by the line number, - * # the nested diagnostic context and finally the message itself. - * # Refer to the documentation of {@link PatternLayout} for further information - * # on the syntax of the ConversionPattern key. - * log4j.appender.A1.layout=org.apache.log4j.PatternLayout - * log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n - * - * # Set options for appender named "A2" - * # A2 should be a RollingFileAppender, with maximum file size of 10 MB - * # using at most one backup file. A2's layout is TTCC, using the - * # ISO8061 date format with context printing enabled. - * log4j.appender.A2=org.apache.log4j.RollingFileAppender - * log4j.appender.A2.MaxFileSize=10MB - * log4j.appender.A2.MaxBackupIndex=1 - * log4j.appender.A2.layout=org.apache.log4j.TTCCLayout - * log4j.appender.A2.layout.ContextPrinting=enabled - * log4j.appender.A2.layout.DateFormat=ISO8601 - * - * # Root logger set to DEBUG using the A2 appender defined above. - * log4j.rootLogger=DEBUG, A2 - * - * # Logger definitions: - * # The SECURITY logger inherits is level from root. However, it's output - * # will go to A1 appender defined above. It's additivity is non-cumulative. - * log4j.logger.SECURITY=INHERIT, A1 + * # The syslog daemon resides on www.abc.net log4j.appender.A1.SyslogHost=www.abc.net + * + * # A1's layout is a PatternLayout, using the conversion pattern # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log + * output will # include # the relative time since the start of the application in # milliseconds, followed by the level + * of the log request, # followed by the two rightmost components of the logger name, # followed by the callers method + * name, followed by the line number, # the nested diagnostic context and finally the message itself. # Refer to the + * documentation of {@link PatternLayout} for further information # on the syntax of the ConversionPattern key. + * log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} + * %M.%L %x - %m\n + * + * # Set options for appender named "A2" # A2 should be a RollingFileAppender, with maximum file size of 10 MB # using + * at most one backup file. A2's layout is TTCC, using the # ISO8061 date format with context printing enabled. + * log4j.appender.A2=org.apache.log4j.RollingFileAppender log4j.appender.A2.MaxFileSize=10MB + * log4j.appender.A2.MaxBackupIndex=1 log4j.appender.A2.layout=org.apache.log4j.TTCCLayout + * log4j.appender.A2.layout.ContextPrinting=enabled log4j.appender.A2.layout.DateFormat=ISO8601 + * + * # Root logger set to DEBUG using the A2 appender defined above. log4j.rootLogger=DEBUG, A2 + * + * # Logger definitions: # The SECURITY logger inherits is level from root. However, it's output # will go to A1 + * appender defined above. It's additivity is non-cumulative. log4j.logger.SECURITY=INHERIT, A1 * log4j.additivity.SECURITY=false * - * # Only warnings or above will be logged for the logger "SECURITY.access". - * # Output will go to A1. + * # Only warnings or above will be logged for the logger "SECURITY.access". # Output will go to A1. * log4j.logger.SECURITY.access=WARN * * - * # The logger "class.of.the.day" inherits its level from the - * # logger hierarchy. Output will go to the appender's of the root - * # logger, A2 in this case. - * log4j.logger.class.of.the.day=INHERIT + * # The logger "class.of.the.day" inherits its level from the # logger hierarchy. Output will go to the appender's of + * the root # logger, A2 in this case. log4j.logger.class.of.the.day=INHERIT * </pre> * - * <p>Refer to the <b>setOption</b> method in each Appender and - * Layout for class specific options. + * <p> + * Refer to the <b>setOption</b> method in each Appender and Layout for class specific options. * - * <p>Use the <code>#</code> or <code>!</code> characters at the - * beginning of a line for comments. + * <p> + * Use the <code>#</code> or <code>!</code> characters at the beginning of a line for comments. * * <p> */ @@ -297,6 +313,12 @@ public class PropertiesConfiguration extends Log4j1Configuration { final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(status); statusConfig.initialize(); + // if log4j.reset=true then reset hierarchy + final String reset = properties.getProperty(RESET_KEY); + if (reset != null && OptionConverter.toBoolean(reset, false)) { + LogManager.resetConfiguration(); + } + configureRoot(properties); parseLoggers(properties); @@ -368,7 +390,7 @@ public class PropertiesConfiguration extends Log4j1Configuration { /** * This method must work for the root category as well. */ - private void parseLogger(final Properties props, final LoggerConfig logger, final String optionKey, final String loggerName, final String value) { + private void parseLogger(final Properties props, final LoggerConfig loggerConfig, final String optionKey, final String loggerName, final String value) { LOGGER.debug("Parsing for [{}] with value=[{}].", loggerName, value); // We must skip over ',' but not white space @@ -386,9 +408,9 @@ public class PropertiesConfiguration extends Log4j1Configuration { final String levelStr = st.nextToken(); LOGGER.debug("Level token is [{}].", levelStr); - final org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR : - OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG); - logger.setLevel(level); + final org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR + : OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG); + loggerConfig.setLevel(level); LOGGER.debug("Logger {} level set to {}", loggerName, level); } @@ -402,9 +424,8 @@ public class PropertiesConfiguration extends Log4j1Configuration { LOGGER.debug("Parsing appender named \"{}\".", appenderName); appender = parseAppender(props, appenderName); if (appender != null) { - LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName, - logger.getName()); - logger.addAppender(getAppender(appenderName), null, null); + LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName, loggerConfig.getName()); + loggerConfig.addAppender(getAppender(appenderName), null, null); } else { LOGGER.debug("Appender named [{}] not found.", appenderName); } @@ -436,8 +457,8 @@ public class PropertiesConfiguration extends Log4j1Configuration { return appender; } - private Appender buildAppender(final String appenderName, final String className, final String prefix, - final String layoutPrefix, final String filterPrefix, final Properties props) { + private Appender buildAppender(final String appenderName, final String className, final String prefix, final String layoutPrefix, final String filterPrefix, + final Properties props) { final Appender appender = newInstanceOf(className, "Appender"); if (appender == null) { return null; @@ -453,7 +474,7 @@ public class PropertiesConfiguration extends Log4j1Configuration { } } appender.addFilter(parseAppenderFilters(props, filterPrefix, appenderName)); - final String[] keys = new String[] { layoutPrefix }; + final String[] keys = new String[] {layoutPrefix}; addProperties(appender, keys, props, prefix); if (appender instanceof AppenderWrapper) { addAppender(((AppenderWrapper) appender).getAppender()); @@ -487,14 +508,14 @@ public class PropertiesConfiguration extends Log4j1Configuration { return layout; } - public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix, - final String errorHandlerClass, final Appender appender) { + public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix, final String errorHandlerClass, final Appender appender) { final ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler"); final String[] keys = new String[] { - errorHandlerPrefix + "." + ROOT_REF, - errorHandlerPrefix + "." + LOGGER_REF, - errorHandlerPrefix + "." + APPENDER_REF_TAG - }; + // @formatter:off + errorHandlerPrefix + "." + ROOT_REF, + errorHandlerPrefix + "." + LOGGER_REF, + errorHandlerPrefix + "." + APPENDER_REF_TAG}; + // @formatter:on addProperties(eh, keys, props, errorHandlerPrefix); return eh; } @@ -515,7 +536,6 @@ public class PropertiesConfiguration extends Log4j1Configuration { PropertySetter.setProperties(obj, edited, prefix + "."); } - public Filter parseAppenderFilters(final Properties props, final String filterPrefix, final String appenderName) { // extract filters and filter options from props into a hashtable mapping // the property name defining the filter class to a list of pre-parsed @@ -577,13 +597,11 @@ public class PropertiesConfiguration extends Log4j1Configuration { return filter; } - private static <T> T newInstanceOf(final String className, final String type) { try { return LoaderUtil.newInstanceOf(className); } catch (ReflectiveOperationException ex) { - LOGGER.error("Unable to create {} {} due to {}:{}", type, className, - ex.getClass().getSimpleName(), ex.getMessage()); + LOGGER.error("Unable to create {} {} due to {}:{}", type, className, ex.getClass().getSimpleName(), ex.getMessage()); return null; } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java new file mode 100644 index 0000000..d14dcf9 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.varia.NullAppender; +import org.junit.jupiter.api.Test; + +/** + * Test {@link BasicConfigurator}. + */ +public class BasicConfiguratorTest { + + @Test + public void testConfigure() { + // TODO More... + BasicConfigurator.configure(); + } + + @Test + public void testResetConfiguration() { + // TODO More... + BasicConfigurator.resetConfiguration(); + } + + @Test + public void testConfigureAppender() { + BasicConfigurator.configure(null); + // TODO More... + } + + @Test + public void testConfigureConsoleAppender() { + // TODO What to do? Map to Log4j 2 Appender deeper in the code? + BasicConfigurator.configure(new ConsoleAppender()); + } + + @Test + public void testConfigureNullAppender() { + // The NullAppender name is null and we do not want an NPE when the name is used as a key in a ConcurrentHashMap. + BasicConfigurator.configure(NullAppender.getNullAppender()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java new file mode 100644 index 0000000..26812ec --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggerRepository; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.OptionHandler; +import org.apache.log4j.spi.RootLogger; +import org.apache.log4j.spi.ThrowableRenderer; +import org.apache.log4j.spi.ThrowableRendererSupport; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link PropertyConfigurator}. + */ +public class PropertyConfiguratorTest { + + /** + * Mock definition of FilterBasedTriggeringPolicy from extras companion. + */ + public static final class FilterBasedTriggeringPolicy extends TriggeringPolicy { + private Filter filter; + + public FilterBasedTriggeringPolicy() { + } + + public Filter getFilter() { + return filter; + + } + + public void setFilter(final Filter val) { + filter = val; + } + } + + /** + * Mock definition of FixedWindowRollingPolicy from extras companion. + */ + public static final class FixedWindowRollingPolicy extends RollingPolicy { + private String activeFileName; + private String fileNamePattern; + private int minIndex; + + public FixedWindowRollingPolicy() { + minIndex = -1; + } + + public String getActiveFileName() { + return activeFileName; + } + + public String getFileNamePattern() { + return fileNamePattern; + } + + public int getMinIndex() { + return minIndex; + } + + public void setActiveFileName(final String val) { + activeFileName = val; + } + + public void setFileNamePattern(final String val) { + fileNamePattern = val; + } + + public void setMinIndex(final int val) { + minIndex = val; + } + } + + /** + * Mock ThrowableRenderer for testThrowableRenderer. See bug 45721. + */ + public static class MockThrowableRenderer implements ThrowableRenderer, OptionHandler { + private boolean activated = false; + private boolean showVersion = true; + + public MockThrowableRenderer() { + } + + @Override + public void activateOptions() { + activated = true; + } + + @Override + public String[] doRender(final Throwable t) { + return new String[0]; + } + + public boolean getShowVersion() { + return showVersion; + } + + public boolean isActivated() { + return activated; + } + + public void setShowVersion(final boolean v) { + showVersion = v; + } + } + + /** + * Mock definition of org.apache.log4j.rolling.RollingFileAppender from extras companion. + */ + public static final class RollingFileAppender extends AppenderSkeleton { + private RollingPolicy rollingPolicy; + private TriggeringPolicy triggeringPolicy; + private boolean append; + + public RollingFileAppender() { + + } + + @Override + public void append(final LoggingEvent event) { + } + + @Override + public void close() { + } + + public boolean getAppend() { + return append; + } + + public RollingPolicy getRollingPolicy() { + return rollingPolicy; + } + + public TriggeringPolicy getTriggeringPolicy() { + return triggeringPolicy; + } + + @Override + public boolean requiresLayout() { + return true; + } + + public void setAppend(final boolean val) { + append = val; + } + + public void setRollingPolicy(final RollingPolicy policy) { + rollingPolicy = policy; + } + + public void setTriggeringPolicy(final TriggeringPolicy policy) { + triggeringPolicy = policy; + } + } + + /** + * Mock definition of org.apache.log4j.rolling.RollingPolicy from extras companion. + */ + public static class RollingPolicy implements OptionHandler { + private boolean activated = false; + + public RollingPolicy() { + + } + + @Override + public void activateOptions() { + activated = true; + } + + public final boolean isActivated() { + return activated; + } + + } + + /** + * Mock definition of TriggeringPolicy from extras companion. + */ + public static class TriggeringPolicy implements OptionHandler { + private boolean activated = false; + + public TriggeringPolicy() { + + } + + @Override + public void activateOptions() { + activated = true; + } + + public final boolean isActivated() { + return activated; + } + + } + + private static final String FILTER1_PROPERTIES = "target/test-classes/log4j1-1.2.17/input/filter1.properties"; + + private static final String CAT_A_NAME = "categoryA"; + + private static final String CAT_B_NAME = "categoryB"; + + private static final String CAT_C_NAME = "categoryC"; + + /** + * Test for bug 40944. Did not catch IllegalArgumentException on Properties.load and close input stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + public void testBadUnicodeEscape() throws IOException { + final String fileName = "target/badescape.properties"; + try (FileWriter writer = new FileWriter(fileName)) { + writer.write("log4j.rootLogger=\\uXX41"); + } + PropertyConfigurator.configure(fileName); + final File file = new File(fileName); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + /** + * Tests configuring Log4J from an InputStream. + * + * @since 1.2.17 + */ + @Test + public void testInputStream() throws IOException { + final Path file = Paths.get(FILTER1_PROPERTIES); + assertTrue(Files.exists(file)); + try (InputStream inputStream = Files.newInputStream(file)) { + PropertyConfigurator.configure(inputStream); + } + this.validateNested(); + LogManager.resetConfiguration(); + } + + /** + * Test for bug 47465. configure(URL) did not close opened JarURLConnection. + * + * @throws IOException if IOException creating properties jar. + */ + @Test + public void testJarURL() throws IOException { + final File dir = new File("output"); + dir.mkdirs(); + final File file = new File("output/properties.jar"); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file))) { + zos.putNextEntry(new ZipEntry(LogManager.DEFAULT_CONFIGURATION_FILE)); + zos.write("log4j.rootLogger=debug".getBytes()); + zos.closeEntry(); + } + final URL url = new URL("jar:" + file.toURI().toURL() + "!/" + LogManager.DEFAULT_CONFIGURATION_FILE); + PropertyConfigurator.configure(url); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + @Test + public void testLocalVsGlobal() { + LoggerRepository repos1, repos2; + final Logger catA = Logger.getLogger(CAT_A_NAME); + final Logger catB = Logger.getLogger(CAT_B_NAME); + final Logger catC = Logger.getLogger(CAT_C_NAME); + + final Properties globalSettings = new Properties(); + globalSettings.put("log4j.logger." + CAT_A_NAME, Level.WARN.toString()); + globalSettings.put("log4j.logger." + CAT_B_NAME, Level.WARN.toString()); + globalSettings.put("log4j.logger." + CAT_C_NAME, Level.DEBUG.toString()); + PropertyConfigurator.configure(globalSettings); + assertEquals(Level.WARN, catA.getLevel()); + assertEquals(Level.WARN, catB.getLevel()); + assertEquals(Level.DEBUG, catC.getLevel()); + + assertEquals(Level.WARN, catA.getLoggerRepository().getLogger(CAT_A_NAME).getLevel()); + assertEquals(Level.WARN, catB.getLoggerRepository().getLogger(CAT_B_NAME).getLevel()); + assertEquals(Level.DEBUG, catC.getLoggerRepository().getLogger(CAT_C_NAME).getLevel()); + + final Properties repos1Settings = new Properties(); + repos1Settings.put("log4j.logger." + CAT_A_NAME, Level.DEBUG.toString()); + repos1Settings.put("log4j.logger." + CAT_B_NAME, Level.INFO.toString()); + repos1 = new Hierarchy(new RootLogger(Level.OFF)); + new PropertyConfigurator().doConfigure(repos1Settings, repos1); + assertEquals(Level.DEBUG, repos1.getLogger(CAT_A_NAME).getLevel()); + assertEquals(Level.INFO, repos1.getLogger(CAT_B_NAME).getLevel()); + + final Properties repos2Settings = new Properties(); + repos2Settings.put("log4j.logger." + CAT_A_NAME, Level.INFO.toString()); + repos2Settings.put("log4j.logger." + CAT_B_NAME, Level.DEBUG.toString()); + repos2 = new Hierarchy(new RootLogger(Level.OFF)); + new PropertyConfigurator().doConfigure(repos2Settings, repos2); + assertEquals(Level.INFO, repos2.getLogger(CAT_A_NAME).getLevel()); + assertEquals(Level.DEBUG, repos2.getLogger(CAT_B_NAME).getLevel()); + } + + /** + * Tests processing of nested objects, see bug 36384. + */ + public void testNested() { + try { + PropertyConfigurator.configure(FILTER1_PROPERTIES); + this.validateNested(); + } finally { + LogManager.resetConfiguration(); + } + } + + /** + * Test processing of log4j.reset property, see bug 17531. + */ + @Test + public void testReset() { + final VectorAppender appender = new VectorAppender(); + appender.setName("A1"); + Logger.getRootLogger().addAppender(appender); + final Properties properties = new Properties(); + properties.put("log4j.reset", "true"); + PropertyConfigurator.configure(properties); + assertNull(Logger.getRootLogger().getAppender("A1")); + LogManager.resetConfiguration(); + } + + /** + * Test of log4j.throwableRenderer support. See bug 45721. + */ + public void testThrowableRenderer() { + final Properties properties = new Properties(); + properties.put("log4j.throwableRenderer", "org.apache.log4j.PropertyConfiguratorTest$MockThrowableRenderer"); + properties.put("log4j.throwableRenderer.showVersion", "false"); + PropertyConfigurator.configure(properties); + final ThrowableRendererSupport repo = (ThrowableRendererSupport) LogManager.getLoggerRepository(); + final MockThrowableRenderer renderer = (MockThrowableRenderer) repo.getThrowableRenderer(); + LogManager.resetConfiguration(); +// assertNotNull(renderer); +// assertEquals(true, renderer.isActivated()); +// assertEquals(false, renderer.getShowVersion()); + } + + /** + * Test for bug 40944. configure(URL) never closed opened stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + public void testURL() throws IOException { + final File file = new File("target/unclosed.properties"); + try (FileWriter writer = new FileWriter(file)) { + writer.write("log4j.rootLogger=debug"); + } + final URL url = file.toURI().toURL(); + PropertyConfigurator.configure(url); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + /** + * Test for bug 40944. configure(URL) did not catch IllegalArgumentException and did not close stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + public void testURLBadEscape() throws IOException { + final File file = new File("target/urlbadescape.properties"); + try (FileWriter writer = new FileWriter(file)) { + writer.write("log4j.rootLogger=\\uXX41"); + } + final URL url = file.toURI().toURL(); + PropertyConfigurator.configure(url); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + void validateNested() { + final Logger logger = Logger.getLogger("org.apache.log4j.PropertyConfiguratorTest"); + final String appenderName = "ROLLING"; + // Appender OK + final Appender appender = logger.getAppender(appenderName); + assertNotNull(appender); + // Down-cast? +// final RollingFileAppender rfa = (RollingFileAppender) appender; +// assertNotNull(appenderName, rfa); +// final FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) rfa.getRollingPolicy(); +// assertEquals("filterBase-test1.log", rollingPolicy.getActiveFileName()); +// assertEquals("filterBased-test1.%i", rollingPolicy.getFileNamePattern()); +// assertEquals(0, rollingPolicy.getMinIndex()); +// assertTrue(rollingPolicy.isActivated()); +// final FilterBasedTriggeringPolicy triggeringPolicy = (FilterBasedTriggeringPolicy) rfa.getTriggeringPolicy(); +// final LevelRangeFilter filter = (LevelRangeFilter) triggeringPolicy.getFilter(); +// assertTrue(Level.INFO.equals(filter.getLevelMin())); + } +}
