blob: 5707e213fb5a64f28de83891c63218ee367b3c5e [file] [log] [blame]
/*
* 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.logging.log4j.junit;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.LoggerContextAccessor;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.selector.ContextSelector;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.test.appender.ListAppender;
import org.apache.logging.log4j.test.junit.CleanFiles;
import org.apache.logging.log4j.test.junit.CleanFolders;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import static org.junit.Assert.*;
/**
* JUnit {@link TestRule} for constructing a new LoggerContext using a specified configuration file. If the system
* property {@code EBUG} is set (e.g., through the command line option {@code -DEBUG}), then the StatusLogger will be
* set to the debug level. This allows for more debug messages as the StatusLogger will be in the error level until a
* configuration file has been read and parsed into a tree of Nodes.
*
* @see LoggerContextSource
* @see Named
*/
public class LoggerContextRule implements TestRule, LoggerContextAccessor {
public static LoggerContextRule createShutdownTimeoutLoggerContextRule(final String config) {
return new LoggerContextRule(config, 10, TimeUnit.SECONDS);
}
private static final String SYS_PROP_KEY_CLASS_NAME = "org.apache.logging.log4j.junit.LoggerContextRule#ClassName";
private static final String SYS_PROP_KEY_DISPLAY_NAME = "org.apache.logging.log4j.junit.LoggerContextRule#DisplayName";
private final String configurationLocation;
private LoggerContext loggerContext;
private Class<? extends ContextSelector> contextSelectorClass;
private String testClassName;
private final long shutdownTimeout;
private final TimeUnit shutdownTimeUnit;
/**
* Constructs a new LoggerContextRule without a configuration file.
*/
public LoggerContextRule() {
this(null, null);
}
/**
* Constructs a new LoggerContextRule for a given configuration file.
*
* @param configurationLocation
* path to configuration file
*/
public LoggerContextRule(final String configurationLocation) {
this(configurationLocation, null);
}
/**
* Constructs a new LoggerContextRule for a given configuration file and a custom {@link ContextSelector} class.
*
* @param configurationLocation
* path to configuration file
* @param contextSelectorClass
* custom ContextSelector class to use instead of default
*/
public LoggerContextRule(final String configurationLocation, final Class<? extends ContextSelector> contextSelectorClass) {
this(configurationLocation, contextSelectorClass, AbstractLifeCycle.DEFAULT_STOP_TIMEOUT,
AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT);
}
public LoggerContextRule(final String configurationLocation, final Class<? extends ContextSelector> contextSelectorClass,
final long shutdownTimeout, final TimeUnit shutdownTimeUnit) {
this.configurationLocation = configurationLocation;
this.contextSelectorClass = contextSelectorClass;
this.shutdownTimeout = shutdownTimeout;
this.shutdownTimeUnit = shutdownTimeUnit;
}
public LoggerContextRule(final String configurationLocation, final int shutdownTimeout, final TimeUnit shutdownTimeUnit) {
this(configurationLocation, null, shutdownTimeout, shutdownTimeUnit);
}
@Override
public Statement apply(final Statement base, final Description description) {
// Hack: Using -DEBUG as a JVM param sets a property called "EBUG"...
if (System.getProperties().containsKey("EBUG")) {
StatusLogger.getLogger().setLevel(Level.DEBUG);
}
testClassName = description.getClassName();
return new Statement() {
@Override
public void evaluate() throws Throwable {
if (contextSelectorClass != null) {
System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, contextSelectorClass.getName());
}
// TODO Consider instead of the above:
// LogManager.setFactory(new Log4jContextFactory(LoaderUtil.newInstanceOf(contextSelectorClass)));
System.setProperty(SYS_PROP_KEY_CLASS_NAME, description.getClassName());
System.setProperty(SYS_PROP_KEY_DISPLAY_NAME, description.getDisplayName());
loggerContext = Configurator.initialize(description.getDisplayName(),
description.getTestClass().getClassLoader(), configurationLocation);
try {
base.evaluate();
} finally {
if (!Configurator.shutdown(loggerContext, shutdownTimeout, shutdownTimeUnit)) {
StatusLogger.getLogger().error("Logger context {} did not shutdown completely after {} {}.",
loggerContext.getName(), shutdownTimeout, shutdownTimeUnit);
}
loggerContext = null;
contextSelectorClass = null;
StatusLogger.getLogger().reset();
System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR);
System.clearProperty(SYS_PROP_KEY_CLASS_NAME);
System.clearProperty(SYS_PROP_KEY_DISPLAY_NAME);
}
}
};
}
/**
* Gets a named Appender for this LoggerContext.
*
* @param name
* the name of the Appender to look up.
* @return the named Appender or {@code null} if it wasn't defined in the configuration.
*/
@SuppressWarnings("unchecked") // Assume the call site knows what it is doing.
public <T extends Appender> T getAppender(final String name) {
return (T) getConfiguration().getAppenders().get(name);
}
/**
* Gets a named Appender for this LoggerContext.
*
* @param <T>
* The target Appender class
* @param name
* the name of the Appender to look up.
* @param cls
* The target Appender class
* @return the named Appender or {@code null} if it wasn't defined in the configuration.
*/
public <T extends Appender> T getAppender(final String name, final Class<T> cls) {
return cls.cast(getConfiguration().getAppenders().get(name));
}
/**
* Gets the associated Configuration for the configuration file this was constructed with.
*
* @return this LoggerContext's Configuration.
*/
public Configuration getConfiguration() {
return loggerContext.getConfiguration();
}
/**
* Gets the configuration location.
*
* @return the configuration location.
*/
public String getConfigurationLocation() {
return configurationLocation;
}
/**
* Gets the current LoggerContext associated with this rule.
*
* @return the current LoggerContext.
*/
@Override
public LoggerContext getLoggerContext() {
return loggerContext;
}
/**
* Gets a named ListAppender or throws an exception for this LoggerContext.
*
* @param name
* the name of the ListAppender to look up.
* @return the named ListAppender.
* @throws AssertionError
* if the named ListAppender doesn't exist or isn't a ListAppender.
*/
public ListAppender getListAppender(final String name) {
final Appender appender = getAppender(name);
if (appender instanceof ListAppender) {
return (ListAppender) appender;
}
throw new AssertionError("No ListAppender named " + name + " found.");
}
/**
* Gets a named Logger using the test class's name from this LoggerContext.
*
* @return the test class's named Logger.
*/
public Logger getLogger() {
return loggerContext.getLogger(testClassName);
}
/**
* Gets a named Logger for the given class in this LoggerContext.
*
* @param clazz
* The Class whose name should be used as the Logger name. If null it will default to the calling class.
* @return the named Logger.
*/
public Logger getLogger(final Class<?> clazz) {
return loggerContext.getLogger(clazz.getName());
}
/**
* Gets a named Logger in this LoggerContext.
*
* @param name
* the name of the Logger to look up or create.
* @return the named Logger.
*/
public Logger getLogger(final String name) {
return loggerContext.getLogger(name);
}
/**
* Gets a named Appender or throws an exception for this LoggerContext.
*
* @param name
* the name of the Appender to look up.
* @return the named Appender.
* @throws AssertionError
* if the Appender doesn't exist.
*/
public Appender getRequiredAppender(final String name) {
final Appender appender = getAppender(name);
assertNotNull("Appender named " + name + " was null.", appender);
return appender;
}
/**
* Gets a named Appender or throws an exception for this LoggerContext.
*
* @param <T>
* The target Appender class
* @param name
* the name of the Appender to look up.
* @param cls
* The target Appender class
* @return the named Appender.
* @throws AssertionError
* if the Appender doesn't exist.
*/
public <T extends Appender> T getRequiredAppender(final String name, final Class<T> cls) {
final T appender = getAppender(name, cls);
assertNotNull("Appender named " + name + " was null in logger context " + loggerContext, appender);
return appender;
}
/**
* Gets the root logger.
*
* @return the root logger.
*/
public Logger getRootLogger() {
return loggerContext.getRootLogger();
}
public void reconfigure() {
loggerContext.reconfigure();
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("LoggerContextRule [configLocation=");
builder.append(configurationLocation);
builder.append(", contextSelectorClass=");
builder.append(contextSelectorClass);
builder.append("]");
return builder.toString();
}
public RuleChain withCleanFilesRule(final String... files) {
return RuleChain.outerRule(new CleanFiles(files)).around(this);
}
public RuleChain withCleanFoldersRule(final boolean before, final boolean after, final int maxTries, final String... folders) {
return RuleChain.outerRule(new CleanFolders(before, after, maxTries, folders)).around(this);
}
public RuleChain withCleanFoldersRule(final String... folders) {
return RuleChain.outerRule(new CleanFolders(folders)).around(this);
}
}