/*
 * 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.composite;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.filter.CompositeFilter;

The default merge strategy for composite configurations.

The default merge strategy performs the merge according to the following rules:

  1. Aggregates the global configuration attributes with those in later configurations replacing those in previous configurations with the exception that the highest status level and the lowest monitorInterval greater than 0 will be used.
  2. Properties from all configurations are aggregated. Duplicate properties replace those in previous configurations.
  3. Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named duplicates may be present.
  4. Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in previous configurations.
  5. Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including all of the Appender's subcomponents.
  6. Loggers are all aggregated. Logger attributes are individually merged with duplicates being replaced by those in later configurations. Appender references on a Logger are aggregated with duplicates being replaced by those in later configurations. Filters on a Logger are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named duplicates may be present. Filters under Appender references included or discarded depending on whether their parent Appender reference is kept or discarded.
/** * The default merge strategy for composite configurations. * <p> * The default merge strategy performs the merge according to the following rules: * <ol> * <li>Aggregates the global configuration attributes with those in later configurations replacing those in previous * configurations with the exception that the highest status level and the lowest monitorInterval greater than 0 will * be used.</li> * <li>Properties from all configurations are aggregated. Duplicate properties replace those in previous * configurations.</li> * <li>Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named * duplicates may be present.</li> * <li>Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in previous * configurations.</li> * <li>Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including * all of the Appender's subcomponents.</li> * <li>Loggers are all aggregated. Logger attributes are individually merged with duplicates being replaced by those * in later configurations. Appender references on a Logger are aggregated with duplicates being replaced by those in * later configurations. Filters on a Logger are aggregated under a CompositeFilter if more than one Filter is defined. * Since Filters are not named duplicates may be present. Filters under Appender references included or discarded * depending on whether their parent Appender reference is kept or discarded.</li> * </ol> */
public class DefaultMergeStrategy implements MergeStrategy { private static final String APPENDERS = "appenders"; private static final String PROPERTIES = "properties"; private static final String LOGGERS = "loggers"; private static final String SCRIPTS = "scripts"; private static final String FILTERS = "filters"; private static final String STATUS = "status"; private static final String NAME = "name"; private static final String REF = "ref";
Merge the root properties.
Params:
  • rootNode – The composite root node.
  • configuration – The configuration to merge.
/** * Merge the root properties. * @param rootNode The composite root node. * @param configuration The configuration to merge. */
@Override public void mergeRootProperties(final Node rootNode, final AbstractConfiguration configuration) { for (final Map.Entry<String, String> attribute : configuration.getRootNode().getAttributes().entrySet()) { boolean isFound = false; for (final Map.Entry<String, String> targetAttribute : rootNode.getAttributes().entrySet()) { if (targetAttribute.getKey().equalsIgnoreCase(attribute.getKey())) { if (attribute.getKey().equalsIgnoreCase(STATUS)) { final Level targetLevel = Level.getLevel(targetAttribute.getValue().toUpperCase()); final Level sourceLevel = Level.getLevel(attribute.getValue().toUpperCase()); if (targetLevel != null && sourceLevel != null) { if (sourceLevel.isLessSpecificThan(targetLevel)) { targetAttribute.setValue(attribute.getValue()); } } else if (sourceLevel != null) { targetAttribute.setValue(attribute.getValue()); } } else { if (attribute.getKey().equalsIgnoreCase("monitorInterval")) { final int sourceInterval = Integer.parseInt(attribute.getValue()); final int targetInterval = Integer.parseInt(targetAttribute.getValue()); if (targetInterval == 0 || sourceInterval < targetInterval) { targetAttribute.setValue(attribute.getValue()); } } else { targetAttribute.setValue(attribute.getValue()); } } isFound = true; } } if (!isFound) { rootNode.getAttributes().put(attribute.getKey(), attribute.getValue()); } } }
Merge the source Configuration into the target Configuration.
Params:
  • target – The target node to merge into.
  • source – The source node.
  • pluginManager – The PluginManager.
/** * Merge the source Configuration into the target Configuration. * * @param target The target node to merge into. * @param source The source node. * @param pluginManager The PluginManager. */
@Override public void mergConfigurations(final Node target, final Node source, final PluginManager pluginManager) { for (final Node sourceChildNode : source.getChildren()) { final boolean isFilter = isFilterNode(sourceChildNode); boolean isMerged = false; for (final Node targetChildNode : target.getChildren()) { if (isFilter) { if (isFilterNode(targetChildNode)) { updateFilterNode(target, targetChildNode, sourceChildNode, pluginManager); isMerged = true; break; } continue; } if (!targetChildNode.getName().equalsIgnoreCase(sourceChildNode.getName())) { continue; } switch (targetChildNode.getName().toLowerCase()) { case PROPERTIES: case SCRIPTS: case APPENDERS: { for (final Node node : sourceChildNode.getChildren()) { for (final Node targetNode : targetChildNode.getChildren()) { if (Objects.equals(targetNode.getAttributes().get(NAME), node.getAttributes().get(NAME))) { targetChildNode.getChildren().remove(targetNode); break; } } targetChildNode.getChildren().add(node); } isMerged = true; break; } case LOGGERS: { final Map<String, Node> targetLoggers = new HashMap<>(); for (final Node node : targetChildNode.getChildren()) { targetLoggers.put(node.getName(), node); } for (final Node node : sourceChildNode.getChildren()) { final Node targetNode = getLoggerNode(targetChildNode, node.getAttributes().get(NAME)); final Node loggerNode = new Node(targetChildNode, node.getName(), node.getType()); if (targetNode != null) { targetNode.getAttributes().putAll(node.getAttributes()); for (final Node sourceLoggerChild : node.getChildren()) { if (isFilterNode(sourceLoggerChild)) { boolean foundFilter = false; for (final Node targetChild : targetNode.getChildren()) { if (isFilterNode(targetChild)) { updateFilterNode(loggerNode, targetChild, sourceLoggerChild, pluginManager); foundFilter = true; break; } } if (!foundFilter) { final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), sourceLoggerChild.getType()); childNode.getAttributes().putAll(sourceLoggerChild.getAttributes()); childNode.getChildren().addAll(sourceLoggerChild.getChildren()); targetNode.getChildren().add(childNode); } } else { final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), sourceLoggerChild.getType()); childNode.getAttributes().putAll(sourceLoggerChild.getAttributes()); childNode.getChildren().addAll(sourceLoggerChild.getChildren()); if (childNode.getName().equalsIgnoreCase("AppenderRef")) { for (final Node targetChild : targetNode.getChildren()) { if (isSameReference(targetChild, childNode)) { targetNode.getChildren().remove(targetChild); break; } } } else { for (final Node targetChild : targetNode.getChildren()) { if (isSameName(targetChild, childNode)) { targetNode.getChildren().remove(targetChild); break; } } } targetNode.getChildren().add(childNode); } } } else { loggerNode.getAttributes().putAll(node.getAttributes()); loggerNode.getChildren().addAll(node.getChildren()); targetChildNode.getChildren().add(loggerNode); } } isMerged = true; break; } default: { targetChildNode.getChildren().addAll(sourceChildNode.getChildren()); isMerged = true; break; } } } if (!isMerged) { if (sourceChildNode.getName().equalsIgnoreCase("Properties")) { target.getChildren().add(0, sourceChildNode); } else { target.getChildren().add(sourceChildNode); } } } } private Node getLoggerNode(final Node parentNode, final String name) { for (final Node node : parentNode.getChildren()) { final String nodeName = node.getAttributes().get(NAME); if (name == null && nodeName == null) { return node; } if (nodeName != null && nodeName.equals(name)) { return node; } } return null; } private void updateFilterNode(final Node target, final Node targetChildNode, final Node sourceChildNode, final PluginManager pluginManager) { if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) { final Node node = new Node(targetChildNode, sourceChildNode.getName(), sourceChildNode.getType()); node.getChildren().addAll(sourceChildNode.getChildren()); node.getAttributes().putAll(sourceChildNode.getAttributes()); targetChildNode.getChildren().add(node); } else { final PluginType pluginType = pluginManager.getPluginType(FILTERS); final Node filtersNode = new Node(targetChildNode, FILTERS, pluginType); final Node node = new Node(filtersNode, sourceChildNode.getName(), sourceChildNode.getType()); node.getAttributes().putAll(sourceChildNode.getAttributes()); final List<Node> children = filtersNode.getChildren(); children.add(targetChildNode); children.add(node); final List<Node> nodes = target.getChildren(); nodes.remove(targetChildNode); nodes.add(filtersNode); } } private boolean isFilterNode(final Node node) { return Filter.class.isAssignableFrom(node.getType().getPluginClass()); } private boolean isSameName(final Node node1, final Node node2) { final String value = node1.getAttributes().get(NAME); return value != null && value.toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase()); } private boolean isSameReference(final Node node1, final Node node2) { final String value = node1.getAttributes().get(REF); return value != null && value.toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase()); } }