blob: 47794fad6215dca6b48e34259d9405e69ba97753 [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.core.config;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.Version;
import org.apache.logging.log4j.core.appender.AsyncAppender;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate;
import org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor;
import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
import org.apache.logging.log4j.core.filter.AbstractFilterable;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.MapLookup;
import org.apache.logging.log4j.core.lookup.StrLookup;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.net.Advertiser;
import org.apache.logging.log4j.core.script.AbstractScript;
import org.apache.logging.log4j.core.script.ScriptManager;
import org.apache.logging.log4j.core.script.ScriptRef;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.util.NameUtil;
import org.apache.logging.log4j.core.util.Source;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.core.util.WatchManager;
import org.apache.logging.log4j.core.util.Watcher;
import org.apache.logging.log4j.core.util.WatcherFactory;
import org.apache.logging.log4j.plugins.Node;
import org.apache.logging.log4j.plugins.util.PluginManager;
import org.apache.logging.log4j.plugins.util.PluginType;
import org.apache.logging.log4j.util.PropertiesUtil;
/**
* The base Configuration. Many configuration implementations will extend this class.
*/
public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
private static final int BUF_SIZE = 16384;
/**
* The root node of the configuration.
*/
protected Node rootNode;
/**
* Listeners for configuration changes.
*/
protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<>();
/**
* Packages found in configuration "packages" attribute.
*/
protected final List<String> pluginPackages = new ArrayList<>();
/**
* The plugin manager.
*/
protected PluginManager pluginManager;
/**
* Shutdown hook is enabled by default.
*/
protected boolean isShutdownHookEnabled = true;
/**
* Shutdown timeout in milliseconds.
*/
protected long shutdownTimeoutMillis = 0;
/**
* The Script manager.
*/
protected ScriptManager scriptManager;
/**
* The Advertiser which exposes appender configurations to external systems.
*/
private Advertiser advertiser = new DefaultAdvertiser();
private Node advertiserNode = null;
private Object advertisement;
private String name;
private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<>();
private ConcurrentMap<String, LoggerConfig> loggerConfigs = new ConcurrentHashMap<>();
private List<CustomLevelConfig> customLevels = Collections.emptyList();
private final ConcurrentMap<String, String> properties = new ConcurrentHashMap<>();
private final StrLookup tempLookup = new Interpolator(properties);
private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
private LoggerConfig root = new LoggerConfig();
private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<>();
private final ConfigurationSource configurationSource;
private final ConfigurationScheduler configurationScheduler = new ConfigurationScheduler();
private final WatchManager watchManager = new WatchManager(configurationScheduler);
private AsyncLoggerConfigDisruptor asyncLoggerConfigDisruptor;
private NanoClock nanoClock = new DummyNanoClock();
private final WeakReference<LoggerContext> loggerContext;
/**
* Constructor.
*/
protected AbstractConfiguration(final LoggerContext loggerContext, final ConfigurationSource configurationSource) {
this.loggerContext = new WeakReference<>(loggerContext);
// The loggerContext is null for the NullConfiguration class.
// this.loggerContext = new WeakReference(Objects.requireNonNull(loggerContext, "loggerContext is null"));
this.configurationSource = Objects.requireNonNull(configurationSource, "configurationSource is null");
componentMap.put(Configuration.CONTEXT_PROPERTIES, properties);
pluginManager = new PluginManager(Node.CATEGORY);
rootNode = new Node();
setState(State.INITIALIZING);
}
@Override
public ConfigurationSource getConfigurationSource() {
return configurationSource;
}
@Override
public List<String> getPluginPackages() {
return pluginPackages;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
@Override
public ScriptManager getScriptManager() {
return scriptManager;
}
public void setScriptManager(final ScriptManager scriptManager) {
this.scriptManager = scriptManager;
}
public PluginManager getPluginManager() {
return pluginManager;
}
public void setPluginManager(final PluginManager pluginManager) {
this.pluginManager = pluginManager;
}
@Override
public WatchManager getWatchManager() {
return watchManager;
}
@Override
public ConfigurationScheduler getScheduler() {
return configurationScheduler;
}
public Node getRootNode() {
return rootNode;
}
@Override
public AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() {
// lazily instantiate only when requested by AsyncLoggers:
// loading AsyncLoggerConfigDisruptor requires LMAX Disruptor jar on classpath
if (asyncLoggerConfigDisruptor == null) {
asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor();
}
return asyncLoggerConfigDisruptor;
}
/**
* Initialize the configuration.
*/
@Override
public void initialize() {
LOGGER.debug(Version.getProductString() + " initializing configuration {}", this);
subst.setConfiguration(this);
try {
scriptManager = new ScriptManager(this, watchManager);
} catch (final LinkageError | Exception e) {
// LOG4J2-1920 ScriptEngineManager is not available in Android
LOGGER.info("Cannot initialize scripting support because this JRE does not support it.", e);
}
pluginManager.collectPlugins(pluginPackages);
final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
levelPlugins.collectPlugins(pluginPackages);
final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
if (plugins != null) {
for (final PluginType<?> type : plugins.values()) {
try {
// Cause the class to be initialized if it isn't already.
Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
} catch (final Exception e) {
LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
.getSimpleName(), e);
}
}
}
setup();
setupAdvertisement();
doConfigure();
setState(State.INITIALIZED);
LOGGER.debug("Configuration {} initialized", this);
}
protected void initializeWatchers(Reconfigurable reconfigurable, ConfigurationSource configSource,
int monitorIntervalSeconds) {
if (configSource.getFile() != null || configSource.getURL() != null) {
if (monitorIntervalSeconds > 0) {
watchManager.setIntervalSeconds(monitorIntervalSeconds);
if (configSource.getFile() != null) {
final Source cfgSource = new Source(configSource);
final long lastModifeid = configSource.getFile().lastModified();
final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(this, reconfigurable,
listeners, lastModifeid);
watchManager.watch(cfgSource, watcher);
} else {
if (configSource.getURL() != null) {
monitorSource(reconfigurable, configSource);
}
}
} else if (watchManager.hasEventListeners() && configSource.getURL() != null && monitorIntervalSeconds >= 0) {
monitorSource(reconfigurable, configSource);
}
}
}
private void monitorSource(Reconfigurable reconfigurable, ConfigurationSource configSource) {
if (configSource.getLastModified() > 0) {
final Source cfgSource = new Source(configSource);
final Watcher watcher = WatcherFactory.getInstance(pluginPackages)
.newWatcher(cfgSource, this, reconfigurable, listeners, configSource.getLastModified());
if (watcher != null) {
watchManager.watch(cfgSource, watcher);
}
} else {
LOGGER.info("{} does not support dynamic reconfiguration", configSource.getURI());
}
}
/**
* Start the configuration.
*/
@Override
public void start() {
// Preserve the prior behavior of initializing during start if not initialized.
if (getState().equals(State.INITIALIZING)) {
initialize();
}
LOGGER.debug("Starting configuration {}", this);
this.setStarting();
if (watchManager.getIntervalSeconds() >= 0) {
watchManager.start();
}
if (hasAsyncLoggers()) {
asyncLoggerConfigDisruptor.start();
}
final Set<LoggerConfig> alreadyStarted = new HashSet<>();
for (final LoggerConfig logger : loggerConfigs.values()) {
logger.start();
alreadyStarted.add(logger);
}
for (final Appender appender : appenders.values()) {
appender.start();
}
if (!alreadyStarted.contains(root)) { // LOG4J2-392
root.start(); // LOG4J2-336
}
super.start();
LOGGER.debug("Started configuration {} OK.", this);
}
private boolean hasAsyncLoggers() {
if (root instanceof AsyncLoggerConfig) {
return true;
}
for (final LoggerConfig logger : loggerConfigs.values()) {
if (logger instanceof AsyncLoggerConfig) {
return true;
}
}
return false;
}
/**
* Tear down the configuration.
*/
@Override
public boolean stop(final long timeout, final TimeUnit timeUnit) {
this.setStopping();
super.stop(timeout, timeUnit, false);
LOGGER.trace("Stopping {}...", this);
// Stop the components that are closest to the application first:
// 1. Notify all LoggerConfigs' ReliabilityStrategy that the configuration will be stopped.
// 2. Stop the LoggerConfig objects (this may stop nested Filters)
// 3. Stop the AsyncLoggerConfigDelegate. This shuts down the AsyncLoggerConfig Disruptor
// and waits until all events in the RingBuffer have been processed.
// 4. Stop all AsyncAppenders. This shuts down the associated thread and
// waits until all events in the queue have been processed. (With optional timeout.)
// 5. Notify all LoggerConfigs' ReliabilityStrategy that appenders will be stopped.
// This guarantees that any event received by a LoggerConfig before reconfiguration
// are passed on to the Appenders before the Appenders are stopped.
// 6. Stop the remaining running Appenders. (It should now be safe to do so.)
// 7. Notify all LoggerConfigs that their Appenders can be cleaned up.
for (final LoggerConfig loggerConfig : loggerConfigs.values()) {
loggerConfig.getReliabilityStrategy().beforeStopConfiguration(this);
}
root.getReliabilityStrategy().beforeStopConfiguration(this);
final String cls = getClass().getSimpleName();
LOGGER.trace("{} notified {} ReliabilityStrategies that config will be stopped.", cls, loggerConfigs.size()
+ 1);
if (!loggerConfigs.isEmpty()) {
LOGGER.trace("{} stopping {} LoggerConfigs.", cls, loggerConfigs.size());
for (final LoggerConfig logger : loggerConfigs.values()) {
logger.stop(timeout, timeUnit);
}
}
LOGGER.trace("{} stopping root LoggerConfig.", cls);
if (!root.isStopped()) {
root.stop(timeout, timeUnit);
}
if (hasAsyncLoggers()) {
LOGGER.trace("{} stopping AsyncLoggerConfigDisruptor.", cls);
asyncLoggerConfigDisruptor.stop(timeout, timeUnit);
}
// Stop the appenders in reverse order in case they still have activity.
final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]);
final List<Appender> async = getAsyncAppenders(array);
if (!async.isEmpty()) {
// LOG4J2-511, LOG4J2-392 stop AsyncAppenders first
LOGGER.trace("{} stopping {} AsyncAppenders.", cls, async.size());
for (final Appender appender : async) {
((LifeCycle) appender).stop(timeout, timeUnit);
}
}
LOGGER.trace("{} notifying ReliabilityStrategies that appenders will be stopped.", cls);
for (final LoggerConfig loggerConfig : loggerConfigs.values()) {
loggerConfig.getReliabilityStrategy().beforeStopAppenders();
}
root.getReliabilityStrategy().beforeStopAppenders();
LOGGER.trace("{} stopping remaining Appenders.", cls);
int appenderCount = 0;
for (int i = array.length - 1; i >= 0; --i) {
if (array[i].isStarted()) { // then stop remaining Appenders
((LifeCycle) array[i]).stop(timeout, timeUnit);
appenderCount++;
}
}
LOGGER.trace("{} stopped {} remaining Appenders.", cls, appenderCount);
LOGGER.trace("{} cleaning Appenders from {} LoggerConfigs.", cls, loggerConfigs.size() + 1);
for (final LoggerConfig loggerConfig : loggerConfigs.values()) {
// LOG4J2-520, LOG4J2-392:
// Important: do not clear appenders until after all AsyncLoggerConfigs
// have been stopped! Stopping the last AsyncLoggerConfig will
// shut down the disruptor and wait for all enqueued events to be processed.
// Only *after this* the appenders can be cleared or events will be lost.
loggerConfig.clearAppenders();
}
root.clearAppenders();
if (watchManager.isStarted()) {
watchManager.stop(timeout, timeUnit);
}
configurationScheduler.stop(timeout, timeUnit);
if (advertiser != null && advertisement != null) {
advertiser.unadvertise(advertisement);
}
setStopped();
LOGGER.debug("Stopped {} OK", this);
return true;
}
private List<Appender> getAsyncAppenders(final Appender[] all) {
final List<Appender> result = new ArrayList<>();
for (int i = all.length - 1; i >= 0; --i) {
if (all[i] instanceof AsyncAppender) {
result.add(all[i]);
}
}
return result;
}
@Override
public boolean isShutdownHookEnabled() {
return isShutdownHookEnabled;
}
@Override
public long getShutdownTimeoutMillis() {
return shutdownTimeoutMillis;
}
public void setup() {
// default does nothing, subclasses do work.
}
protected Level getDefaultStatus() {
final String statusLevel = PropertiesUtil.getProperties().getStringProperty(
Constants.LOG4J_DEFAULT_STATUS_LEVEL, Level.ERROR.name());
try {
return Level.toLevel(statusLevel);
} catch (final Exception ex) {
return Level.ERROR;
}
}
protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource,
final byte[] buffer, final String contentType) {
if (advertiserString != null) {
final Node node = new Node(null, advertiserString, null);
final Map<String, String> attributes = node.getAttributes();
attributes.put("content", new String(buffer));
attributes.put("contentType", contentType);
attributes.put("name", "configuration");
if (configSource.getLocation() != null) {
attributes.put("location", configSource.getLocation());
}
advertiserNode = node;
}
}
private void setupAdvertisement() {
if (advertiserNode != null) {
final String nodeName = advertiserNode.getName();
final PluginType<?> type = pluginManager.getPluginType(nodeName);
if (type != null) {
final Class<? extends Advertiser> clazz = type.getPluginClass().asSubclass(Advertiser.class);
try {
advertiser = clazz.newInstance();
advertisement = advertiser.advertise(advertiserNode.getAttributes());
} catch (final InstantiationException e) {
LOGGER.error("InstantiationException attempting to instantiate advertiser: {}", nodeName, e);
} catch (final IllegalAccessException e) {
LOGGER.error("IllegalAccessException attempting to instantiate advertiser: {}", nodeName, e);
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T getComponent(final String componentName) {
return (T) componentMap.get(componentName);
}
@Override
public void addComponent(final String componentName, final Object obj) {
componentMap.putIfAbsent(componentName, obj);
}
protected void preConfigure(final Node node) {
try {
for (final Node child : node.getChildren()) {
if (child.getType() == null) {
LOGGER.error("Unable to locate plugin type for " + child.getName());
continue;
}
final Class<?> clazz = child.getType().getPluginClass();
if (clazz.isAnnotationPresent(Scheduled.class)) {
configurationScheduler.incrementScheduledItems();
}
preConfigure(child);
}
} catch (final Exception ex) {
LOGGER.error("Error capturing node data for node " + node.getName(), ex);
}
}
protected void doConfigure() {
preConfigure(rootNode);
configurationScheduler.start();
if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
final Node first = rootNode.getChildren().get(0);
createConfiguration(first, null);
if (first.getObject() != null) {
subst.setVariableResolver((StrLookup) first.getObject());
}
} else {
final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
final StrLookup lookup = map == null ? null : new MapLookup(map);
subst.setVariableResolver(new Interpolator(lookup, pluginPackages));
}
boolean setLoggers = false;
boolean setRoot = false;
for (final Node child : rootNode.getChildren()) {
if (child.getName().equalsIgnoreCase("Properties")) {
if (tempLookup == subst.getVariableResolver()) {
LOGGER.error("Properties declaration must be the first element in the configuration");
}
continue;
}
createConfiguration(child, null);
if (child.getObject() == null) {
continue;
}
if (child.getName().equalsIgnoreCase("Scripts")) {
for (final AbstractScript script : child.getObject(AbstractScript[].class)) {
if (script instanceof ScriptRef) {
LOGGER.error("Script reference to {} not added. Scripts definition cannot contain script references",
script.getName());
} else {
if (scriptManager != null) {
scriptManager.addScript(script);
}}
}
} else if (child.getName().equalsIgnoreCase("Appenders")) {
appenders = child.getObject();
} else if (child.isInstanceOf(Filter.class)) {
addFilter(child.getObject(Filter.class));
} else if (child.getName().equalsIgnoreCase("Loggers")) {
final Loggers l = child.getObject();
loggerConfigs = l.getMap();
setLoggers = true;
if (l.getRoot() != null) {
root = l.getRoot();
setRoot = true;
}
} else if (child.getName().equalsIgnoreCase("CustomLevels")) {
customLevels = child.getObject(CustomLevels.class).getCustomLevels();
} else if (child.isInstanceOf(CustomLevelConfig.class)) {
final List<CustomLevelConfig> copy = new ArrayList<>(customLevels);
copy.add(child.getObject(CustomLevelConfig.class));
customLevels = copy;
} else {
final List<String> expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"",
"\"Scripts\"", "\"CustomLevels\"");
LOGGER.error("Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.",
child.getName(), child.getObject().getClass().getName(), expected);
}
}
if (!setLoggers) {
LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
setToDefault();
return;
} else if (!setRoot) {
LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
setToDefault();
// return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
}
for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
final LoggerConfig loggerConfig = entry.getValue();
for (final AppenderRef ref : loggerConfig.getAppenderRefs()) {
final Appender app = appenders.get(ref.getRef());
if (app != null) {
loggerConfig.addAppender(app, ref.getLevel(), ref.getFilter());
} else {
LOGGER.error("Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(),
loggerConfig);
}
}
}
setParents();
}
protected void setToDefault() {
// LOG4J2-1176 facilitate memory leak investigation
setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
.setPattern(DefaultConfiguration.DEFAULT_PATTERN)
.setConfiguration(this)
.build();
final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
appender.start();
addAppender(appender);
final LoggerConfig rootLoggerConfig = getRootLogger();
rootLoggerConfig.addAppender(appender, null, null);
final Level defaultLevel = Level.ERROR;
final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL,
defaultLevel.name());
final Level level = Level.valueOf(levelName);
rootLoggerConfig.setLevel(level != null ? level : defaultLevel);
}
/**
* Set the name of the configuration.
*
* @param name The name.
*/
public void setName(final String name) {
this.name = name;
}
/**
* Returns the name of the configuration.
*
* @return the name of the configuration.
*/
@Override
public String getName() {
return name;
}
/**
* Add a listener for changes on the configuration.
*
* @param listener The ConfigurationListener to add.
*/
@Override
public void addListener(final ConfigurationListener listener) {
listeners.add(listener);
}
/**
* Remove a ConfigurationListener.
*
* @param listener The ConfigurationListener to remove.
*/
@Override
public void removeListener(final ConfigurationListener listener) {
listeners.remove(listener);
}
/**
* Returns the Appender with the specified name.
*
* @param appenderName The name of the Appender.
* @return the Appender with the specified name or null if the Appender cannot be located.
*/
@Override
@SuppressWarnings("unchecked")
public <T extends Appender> T getAppender(final String appenderName) {
return appenderName != null ? (T) appenders.get(appenderName) : null;
}
/**
* Returns a Map containing all the Appenders and their name.
*
* @return A Map containing each Appender's name and the Appender object.
*/
@Override
public Map<String, Appender> getAppenders() {
return appenders;
}
/**
* Adds an Appender to the configuration.
*
* @param appender The Appender to add.
*/
@Override
public void addAppender(final Appender appender) {
if (appender != null) {
appenders.putIfAbsent(appender.getName(), appender);
}
}
@Override
public StrSubstitutor getStrSubstitutor() {
return subst;
}
@Override
public void setAdvertiser(final Advertiser advertiser) {
this.advertiser = advertiser;
}
@Override
public Advertiser getAdvertiser() {
return advertiser;
}
/*
* (non-Javadoc)
*
* @see org.apache.logging.log4j.core.config.ReliabilityStrategyFactory#getReliabilityStrategy(org.apache.logging.log4j
* .core.config.LoggerConfig)
*/
@Override
public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfig) {
return ReliabilityStrategyFactory.getReliabilityStrategy(loggerConfig);
}
/**
* Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is
* being updated at the same time.
*
* Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
*
* @param logger The Logger the Appender will be associated with.
* @param appender The Appender.
*/
@Override
public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
final Appender appender) {
if (appender == null || logger == null) {
return;
}
final String loggerName = logger.getName();
appenders.putIfAbsent(appender.getName(), appender);
final LoggerConfig lc = getLoggerConfig(loggerName);
if (lc.getName().equals(loggerName)) {
lc.addAppender(appender, null, null);
} else {
final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
nlc.addAppender(appender, null, null);
nlc.setParent(lc);
loggerConfigs.putIfAbsent(loggerName, nlc);
setParents();
logger.getContext().updateLoggers();
}
}
/**
* Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being
* updated at the same time.
*
* Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
*
* @param logger The Logger the Footer will be associated with.
* @param filter The Filter.
*/
@Override
public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
final String loggerName = logger.getName();
final LoggerConfig lc = getLoggerConfig(loggerName);
if (lc.getName().equals(loggerName)) {
lc.addFilter(filter);
} else {
final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
nlc.addFilter(filter);
nlc.setParent(lc);
loggerConfigs.putIfAbsent(loggerName, nlc);
setParents();
logger.getContext().updateLoggers();
}
}
/**
* Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being
* updated at the same time.
*
* Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
*
* @param logger The Logger the Appender will be associated with.
* @param additive True if the LoggerConfig should be additive, false otherwise.
*/
@Override
public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) {
final String loggerName = logger.getName();
final LoggerConfig lc = getLoggerConfig(loggerName);
if (lc.getName().equals(loggerName)) {
lc.setAdditive(additive);
} else {
final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), additive);
nlc.setParent(lc);
loggerConfigs.putIfAbsent(loggerName, nlc);
setParents();
logger.getContext().updateLoggers();
}
}
/**
* Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes the Appender
* from this appender list and then stops the appender. This method is synchronized in case an Appender with the
* same name is being added during the removal.
*
* @param appenderName the name of the appender to remove.
*/
public synchronized void removeAppender(final String appenderName) {
for (final LoggerConfig logger : loggerConfigs.values()) {
logger.removeAppender(appenderName);
}
final Appender app = appenderName != null ? appenders.remove(appenderName) : null;
if (app != null) {
app.stop();
}
}
/*
* (non-Javadoc)
*
* @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels()
*/
@Override
public List<CustomLevelConfig> getCustomLevels() {
return Collections.unmodifiableList(customLevels);
}
/**
* Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the package name as
* necessary or return the root LoggerConfig if no other matches were found.
*
* @param loggerName The Logger name.
* @return The located LoggerConfig.
*/
@Override
public LoggerConfig getLoggerConfig(final String loggerName) {
LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
if (loggerConfig != null) {
return loggerConfig;
}
String substr = loggerName;
while ((substr = NameUtil.getSubName(substr)) != null) {
loggerConfig = loggerConfigs.get(substr);
if (loggerConfig != null) {
return loggerConfig;
}
}
return root;
}
@Override
public LoggerContext getLoggerContext() {
return loggerContext.get();
}
/**
* Returns the root Logger.
*
* @return the root Logger.
*/
@Override
public LoggerConfig getRootLogger() {
return root;
}
/**
* Returns a Map of all the LoggerConfigs.
*
* @return a Map with each entry containing the name of the Logger and the LoggerConfig.
*/
@Override
public Map<String, LoggerConfig> getLoggers() {
return Collections.unmodifiableMap(loggerConfigs);
}
/**
* Returns the LoggerConfig with the specified name.
*
* @param loggerName The Logger name.
* @return The LoggerConfig or null if no match was found.
*/
public LoggerConfig getLogger(final String loggerName) {
return loggerConfigs.get(loggerName);
}
/**
* Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc. After addLogger is
* called LoggerContext.updateLoggers must be called.
*
* @param loggerName The name of the Logger.
* @param loggerConfig The LoggerConfig.
*/
@Override
public synchronized void addLogger(final String loggerName, final LoggerConfig loggerConfig) {
loggerConfigs.putIfAbsent(loggerName, loggerConfig);
setParents();
}
/**
* Remove a LoggerConfig.
*
* @param loggerName The name of the Logger.
*/
@Override
public synchronized void removeLogger(final String loggerName) {
loggerConfigs.remove(loggerName);
setParents();
}
@Override
public void createConfiguration(final Node node, final LogEvent event) {
final PluginType<?> type = node.getType();
if (type != null && type.isDeferChildren()) {
node.setObject(createPluginObject(type, node, event));
} else {
for (final Node child : node.getChildren()) {
createConfiguration(child, event);
}
if (type == null) {
if (node.getParent() != null) {
LOGGER.error("Unable to locate plugin for {}", node.getName());
}
} else {
node.setObject(createPluginObject(type, node, event));
}
}
}
/**
* Invokes a static factory method to either create the desired object or to create a builder object that creates
* the desired object. In the case of a factory method, it should be annotated with
* {@link org.apache.logging.log4j.plugins.PluginFactory}, and each parameter should be annotated with
* an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with
* {@link org.apache.logging.log4j.plugins.PluginAttribute} must be a type that can be converted from a
* string using one of the {@link org.apache.logging.log4j.plugins.convert.TypeConverter TypeConverters}
* . Parameters with {@link org.apache.logging.log4j.plugins.PluginElement} may be any plugin class or
* an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is
* called can create these from an array.
*
* Plugins can also be created using a builder class that implements
* {@link org.apache.logging.log4j.plugins.util.Builder}. In that case, a static method annotated with
* {@link org.apache.logging.log4j.plugins.PluginBuilderAttribute} should create the builder class, and
* the various fields in the builder class should be annotated similarly to the method parameters. However, instead
* of using PluginAttribute, one should use
* {@link org.apache.logging.log4j.plugins.PluginBuilderAttribute} where the default value can be
* specified as the default field value instead of as an additional annotation parameter.
*
* In either case, there are also annotations for specifying a
* {@link org.apache.logging.log4j.core.config.Configuration} (
* {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a
* {@link org.apache.logging.log4j.plugins.Node} (
* {@link org.apache.logging.log4j.plugins.PluginNode}).
*
* Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally
* result in unhelpful InvocationTargetExceptions.
*
* @param type the type of plugin to create.
* @param node the corresponding configuration node for this plugin to create.
* @param event the LogEvent that spurred the creation of this plugin
* @return the created plugin object or {@code null} if there was an error setting it up.
* @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
* @see org.apache.logging.log4j.plugins.inject.ConfigurationInjector
* @see org.apache.logging.log4j.plugins.convert.TypeConverter
*/
private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
final Class<?> clazz = type.getPluginClass();
if (Map.class.isAssignableFrom(clazz)) {
try {
return createPluginMap(node);
} catch (final Exception e) {
LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e);
}
}
if (Collection.class.isAssignableFrom(clazz)) {
try {
return createPluginCollection(node);
} catch (final Exception e) {
LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e);
}
}
return new PluginBuilder(type).setConfiguration(this).setConfigurationNode(node).forLogEvent(event).build();
}
private static Map<String, ?> createPluginMap(final Node node) {
final Map<String, Object> map = new LinkedHashMap<>();
for (final Node child : node.getChildren()) {
final Object object = child.getObject();
map.put(child.getName(), object);
}
return map;
}
private static Collection<?> createPluginCollection(final Node node) {
final List<Node> children = node.getChildren();
final Collection<Object> list = new ArrayList<>(children.size());
for (final Node child : children) {
final Object object = child.getObject();
list.add(object);
}
return list;
}
private void setParents() {
for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
final LoggerConfig logger = entry.getValue();
String key = entry.getKey();
if (!key.isEmpty()) {
final int i = key.lastIndexOf('.');
if (i > 0) {
key = key.substring(0, i);
LoggerConfig parent = getLoggerConfig(key);
if (parent == null) {
parent = root;
}
logger.setParent(parent);
} else {
logger.setParent(root);
}
}
}
}
/**
* Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open after
* invocation of this method.
*
* @param is the InputStream to read into a byte array buffer.
* @return a byte array of the InputStream contents.
* @throws IOException if the {@code read} method of the provided InputStream throws this exception.
*/
protected static byte[] toByteArray(final InputStream is) throws IOException {
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
final byte[] data = new byte[BUF_SIZE];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
}
@Override
public NanoClock getNanoClock() {
return nanoClock;
}
@Override
public void setNanoClock(final NanoClock nanoClock) {
this.nanoClock = Objects.requireNonNull(nanoClock, "nanoClock");
}
}