This is an automated email from the ASF dual-hosted git repository.
ckozak pushed a commit to branch log4j-2.3.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/log4j-2.3.x by this push:
new ce6b78d Fix string substitution recursion (#641)
ce6b78d is described below
commit ce6b78d082aae89089cb3ad25cdd46e9ec70a70b
Author: Carter Kozak <[email protected]>
AuthorDate: Sun Dec 19 01:42:37 2021 -0500
Fix string substitution recursion (#641)
cherry-pick of 806023265f8c905b2dd1d81fd2458f64b2ea0b5e
---
.../log4j/core/config/AbstractConfiguration.java | 17 +-
.../logging/log4j/core/config/AppenderControl.java | 6 +-
.../log4j/core/config/ConfigurationFactory.java | 3 +-
.../log4j/core/config/json/JsonConfiguration.java | 2 +-
.../core/config/plugins/util/PluginBuilder.java | 10 +-
.../log4j/core/config/xml/XmlConfiguration.java | 2 +-
.../core/lookup/ConfigurationStrSubstitutor.java | 63 ++++
.../log4j/core/lookup/ContextMapLookup.java | 2 +-
.../logging/log4j/core/lookup/DateLookup.java | 2 +-
.../logging/log4j/core/lookup/Interpolator.java | 2 +-
.../log4j/core/lookup/RuntimeStrSubstitutor.java | 61 ++++
.../logging/log4j/core/lookup/StrSubstitutor.java | 396 ++++++++++++++-------
...rnResolverDoesNotEvaluateThreadContextTest.java | 117 ++++++
.../RoutingAppenderKeyLookupEvaluationTest.java | 94 +++++
.../log4j/core/lookup/StrSubstitutorTest.java | 138 ++++++-
...{log4j-routing.xml => log4j-routing-lookup.xml} | 34 +-
log4j-core/src/test/resources/log4j-routing.json | 5 +-
log4j-core/src/test/resources/log4j-routing.xml | 5 +-
log4j-core/src/test/resources/log4j-routing2.json | 5 +-
.../log4j2-pattern-layout-with-context.xml | 34 ++
.../logging/log4j/web/Log4jWebInitializerImpl.java | 3 +-
src/changes/changes.xml | 3 +
22 files changed, 826 insertions(+), 178 deletions(-)
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
index d87e4b9..c4c965a 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
@@ -48,8 +48,10 @@ import
org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.filter.AbstractFilterable;
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.MapLookup;
+import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor;
import org.apache.logging.log4j.core.lookup.StrLookup;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.net.Advertiser;
@@ -102,7 +104,8 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
private List<CustomLevelConfig> customLevels = Collections.emptyList();
private final ConcurrentMap<String, String> properties = new
ConcurrentHashMap<String, String>();
private final StrLookup tempLookup = new Interpolator(properties);
- private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
+ private final StrSubstitutor subst = new RuntimeStrSubstitutor(tempLookup);
+ private final StrSubstitutor configurationStrSubstitutor = new
ConfigurationStrSubstitutor(subst);
private LoggerConfig root = new LoggerConfig();
private final ConcurrentMap<String, Object> componentMap = new
ConcurrentHashMap<String, Object>();
protected final List<String> pluginPackages = new ArrayList<String>();
@@ -338,12 +341,16 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
final Node first = rootNode.getChildren().get(0);
createConfiguration(first, null);
if (first.getObject() != null) {
- subst.setVariableResolver((StrLookup) first.getObject());
+ StrLookup lookup = (StrLookup) first.getObject();
+ subst.setVariableResolver(lookup);
+ configurationStrSubstitutor.setVariableResolver(lookup);
}
} 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));
+ Interpolator interpolator = new Interpolator(lookup,
pluginPackages);
+ subst.setVariableResolver(interpolator);
+ configurationStrSubstitutor.setVariableResolver(interpolator);
}
boolean setLoggers = false;
@@ -496,6 +503,10 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
return subst;
}
+ public StrSubstitutor getConfigurationStrSubstitutor() {
+ return configurationStrSubstitutor;
+ }
+
@Override
public void setConfigurationMonitor(final ConfigurationMonitor monitor) {
this.monitor = monitor;
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
index ce4b9fb..5b04e7f 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
@@ -102,10 +102,10 @@ public class AppenderControl extends AbstractFilterable {
if (!appender.ignoreExceptions()) {
throw ex;
}
- } catch (final Exception ex) {
- appender.getHandler().error("An exception occurred processing
Appender " + appender.getName(), ex);
+ } catch (final Throwable t) {
+ appender.getHandler().error("An exception occurred processing
Appender " + appender.getName(), t);
if (!appender.ignoreExceptions()) {
- throw new AppenderLoggingException(ex);
+ throw new AppenderLoggingException(t);
}
}
} finally {
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
index 29933d7..7897823 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
@@ -37,6 +37,7 @@ import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
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.lookup.ConfigurationStrSubstitutor;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.util.FileUtils;
@@ -117,7 +118,7 @@ public abstract class ConfigurationFactory {
private static ConfigurationFactory configFactory = new Factory();
- protected final StrSubstitutor substitutor = new StrSubstitutor(new
Interpolator());
+ protected final StrSubstitutor substitutor = new
ConfigurationStrSubstitutor(new Interpolator());
private static final Lock LOCK = new ReentrantLock();
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
index c9a8726..cb6b51f 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
@@ -73,7 +73,7 @@ public class JsonConfiguration extends AbstractConfiguration
implements Reconfig
.withStatus(getDefaultStatus());
for (final Map.Entry<String, String> entry :
rootNode.getAttributes().entrySet()) {
final String key = entry.getKey();
- final String value =
getStrSubstitutor().replace(entry.getValue());
+ final String value =
getConfigurationStrSubstitutor().replace(entry.getValue());
// TODO: this duplicates a lot of the XmlConfiguration
constructor
if ("status".equalsIgnoreCase(key)) {
statusConfig.withStatus(value);
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
index 5730c45..c74ddc0 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
@@ -39,12 +39,12 @@ import
org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidat
import
org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators;
import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
import org.apache.logging.log4j.core.util.Assert;
import org.apache.logging.log4j.core.util.Builder;
import org.apache.logging.log4j.core.util.ReflectionUtil;
import org.apache.logging.log4j.core.util.TypeUtil;
import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Chars;
import org.apache.logging.log4j.util.StringBuilders;
/**
@@ -186,7 +186,9 @@ public class PluginBuilder implements Builder<Object> {
final Object value = visitor.setAliases(aliases)
.setAnnotation(a)
.setConversionType(field.getType())
- .setStrSubstitutor(configuration.getStrSubstitutor())
+ .setStrSubstitutor(event == null
+ ? new
ConfigurationStrSubstitutor(configuration.getStrSubstitutor())
+ : configuration.getStrSubstitutor())
.setMember(field)
.visit(configuration, node, event, log);
// don't overwrite default values if the visitor gives us
no value to inject
@@ -247,7 +249,9 @@ public class PluginBuilder implements Builder<Object> {
final Object value = visitor.setAliases(aliases)
.setAnnotation(a)
.setConversionType(types[i])
- .setStrSubstitutor(configuration.getStrSubstitutor())
+ .setStrSubstitutor(event == null
+ ? new
ConfigurationStrSubstitutor(configuration.getStrSubstitutor())
+ : configuration.getStrSubstitutor())
.setMember(factory)
.visit(configuration, node, event, log);
// don't overwrite existing values if the visitor gives us
no value to inject
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
index 2425508..6099008 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
@@ -144,7 +144,7 @@ public class XmlConfiguration extends AbstractConfiguration
implements Reconfigu
.withStatus(getDefaultStatus());
for (final Map.Entry<String, String> entry : attrs.entrySet()) {
final String key = entry.getKey();
- final String value =
getStrSubstitutor().replace(entry.getValue());
+ final String value =
getConfigurationStrSubstitutor().replace(entry.getValue());
if ("status".equalsIgnoreCase(key)) {
statusConfig.withStatus(value);
} else if ("dest".equalsIgnoreCase(key)) {
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java
new file mode 100644
index 0000000..1287721
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.lookup;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * {@link RuntimeStrSubstitutor} is a {@link StrSubstitutor} which only
supports recursive evaluation of lookups.
+ * This can be dangerous when combined with user-provided inputs, and should
only be used on data directly from
+ * a configuration.
+ */
+public final class ConfigurationStrSubstitutor extends StrSubstitutor {
+
+ public ConfigurationStrSubstitutor() {
+ }
+
+ public ConfigurationStrSubstitutor(final Map<String, String> valueMap) {
+ super(valueMap);
+ }
+
+ public ConfigurationStrSubstitutor(final Properties properties) {
+ super(properties);
+ }
+
+ public ConfigurationStrSubstitutor(final StrLookup lookup) {
+ super(lookup);
+ }
+
+ public ConfigurationStrSubstitutor(final StrSubstitutor other) {
+ super(other);
+ }
+
+ @Override
+ boolean isRecursiveEvaluationAllowed() {
+ return true;
+ }
+
+ @Override
+ void setRecursiveEvaluationAllowed(final boolean
recursiveEvaluationAllowed) {
+ throw new UnsupportedOperationException(
+ "recursiveEvaluationAllowed cannot be modified within
ConfigurationStrSubstitutor");
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigurationStrSubstitutor{" + super.toString() + "}";
+ }
+}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
index 9a39338..4c2e585 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
@@ -44,6 +44,6 @@ public class ContextMapLookup implements StrLookup {
*/
@Override
public String lookup(final LogEvent event, final String key) {
- return event.getContextMap().get(key);
+ return event == null ? null : event.getContextMap().get(key);
}
}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
index 3e630b0..3bcecf7 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
@@ -54,7 +54,7 @@ public class DateLookup implements StrLookup {
*/
@Override
public String lookup(final LogEvent event, final String key) {
- return formatDate(event.getTimeMillis(), key);
+ return event == null ? lookup(key) : formatDate(event.getTimeMillis(),
key);
}
private String formatDate(final long date, final String format) {
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
index b0348e8..539597b 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
@@ -36,7 +36,7 @@ public class Interpolator extends AbstractLookup {
private static final Logger LOGGER = StatusLogger.getLogger();
/** Constant for the prefix separator. */
- private static final char PREFIX_SEPARATOR = ':';
+ static final char PREFIX_SEPARATOR = ':';
private final Map<String, StrLookup> lookups = new HashMap<String,
StrLookup>();
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java
new file mode 100644
index 0000000..f002c6e
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java
@@ -0,0 +1,61 @@
+/*
+ * 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.lookup;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * {@link RuntimeStrSubstitutor} is a {@link StrSubstitutor} which only
supports evaluation of top-level lookups.
+ */
+public final class RuntimeStrSubstitutor extends StrSubstitutor {
+
+ public RuntimeStrSubstitutor() {
+ }
+
+ public RuntimeStrSubstitutor(final Map<String, String> valueMap) {
+ super(valueMap);
+ }
+
+ public RuntimeStrSubstitutor(final Properties properties) {
+ super(properties);
+ }
+
+ public RuntimeStrSubstitutor(final StrLookup lookup) {
+ super(lookup);
+ }
+
+ public RuntimeStrSubstitutor(final StrSubstitutor other) {
+ super(other);
+ }
+
+ @Override
+ boolean isRecursiveEvaluationAllowed() {
+ return false;
+ }
+
+ @Override
+ void setRecursiveEvaluationAllowed(final boolean
recursiveEvaluationAllowed) {
+ throw new UnsupportedOperationException(
+ "recursiveEvaluationAllowed cannot be modified within
RuntimeStrSubstitutor");
+ }
+
+ @Override
+ public String toString() {
+ return "RuntimeStrSubstitutor{" + super.toString() + "}";
+ }
+}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
index 77362bc..ffa93a0 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
@@ -25,6 +25,8 @@ import java.util.Map;
import java.util.Properties;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Strings;
/**
@@ -142,21 +144,25 @@ public class StrSubstitutor {
* Constant for the default escape character.
*/
public static final char DEFAULT_ESCAPE = '$';
-
+
/**
* Constant for the default variable prefix.
*/
public static final StrMatcher DEFAULT_PREFIX =
StrMatcher.stringMatcher(DEFAULT_ESCAPE + "{");
-
+
/**
* Constant for the default variable suffix.
*/
public static final StrMatcher DEFAULT_SUFFIX =
StrMatcher.stringMatcher("}");
-
+
/**
* Constant for the default value delimiter of a variable.
*/
- public static final StrMatcher DEFAULT_VALUE_DELIMITER =
StrMatcher.stringMatcher(":-");
+ public static final String DEFAULT_VALUE_DELIMITER_STRING = ":-";
+ public static final StrMatcher DEFAULT_VALUE_DELIMITER =
StrMatcher.stringMatcher(DEFAULT_VALUE_DELIMITER_STRING);
+
+ public static final String ESCAPE_DELIMITER_STRING = ":\\-";
+ public static final StrMatcher DEFAULT_VALUE_ESCAPE_DELIMITER =
StrMatcher.stringMatcher(ESCAPE_DELIMITER_STRING);
private static final int BUF_SIZE = 256;
@@ -164,26 +170,44 @@ public class StrSubstitutor {
* Stores the escape character.
*/
private char escapeChar;
+
/**
* Stores the variable prefix.
*/
private StrMatcher prefixMatcher;
+
/**
* Stores the variable suffix.
*/
private StrMatcher suffixMatcher;
+
/**
* Stores the default variable value delimiter
*/
+ private String valueDelimiterString;
private StrMatcher valueDelimiterMatcher;
+
+ /**
+ * Escape string to avoid matching the value delimiter matcher;
+ */
+ private StrMatcher valueEscapeDelimiterMatcher;
+
/**
* Variable resolution is delegated to an implementer of VariableResolver.
*/
private StrLookup variableResolver;
+
/**
* The flag whether substitution in variable names is enabled.
*/
- private boolean enableSubstitutionInVariables;
+ private boolean enableSubstitutionInVariables = false;
+
+ /**
+ * The currently active Configuration for use by ConfigurationAware
StrLookup implementations.
+ */
+ private Configuration configuration;
+
+ private boolean recursiveEvaluationAllowed;
//-----------------------------------------------------------------------
/**
@@ -193,6 +217,7 @@ public class StrSubstitutor {
public StrSubstitutor() {
this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
}
+
/**
* Creates a new instance and initializes it. Uses defaults for variable
* prefix and suffix and the escaping character.
@@ -245,6 +270,16 @@ public class StrSubstitutor {
}
/**
+ * Creates a new instance and initializes it. Uses defaults for variable
+ * prefix and suffix and the escaping character.
+ *
+ * @param properties the map with the variables' values, may be null
+ */
+ public StrSubstitutor(final Properties properties) {
+ this(toTypeSafeMap(properties));
+ }
+
+ /**
* Creates a new instance and initializes it.
*
* @param variableResolver the variable resolver, may be null
@@ -300,7 +335,9 @@ public class StrSubstitutor {
public StrSubstitutor(final StrLookup variableResolver, final StrMatcher
prefixMatcher,
final StrMatcher suffixMatcher,
final char escape) {
- this(variableResolver, prefixMatcher, suffixMatcher, escape,
DEFAULT_VALUE_DELIMITER);
+ this(variableResolver, prefixMatcher, suffixMatcher, escape,
DEFAULT_VALUE_DELIMITER,
+ DEFAULT_VALUE_ESCAPE_DELIMITER);
+ this.valueDelimiterString = DEFAULT_VALUE_DELIMITER_STRING;
}
/**
@@ -313,13 +350,48 @@ public class StrSubstitutor {
* @param valueDelimiterMatcher the variable default value delimiter
matcher, may be null
* @throws IllegalArgumentException if the prefix or suffix is null
*/
- public StrSubstitutor(
- final StrLookup variableResolver, final StrMatcher prefixMatcher,
final StrMatcher suffixMatcher, final char escape, final StrMatcher
valueDelimiterMatcher) {
+ public StrSubstitutor(final StrLookup variableResolver, final StrMatcher
prefixMatcher,
+ final StrMatcher suffixMatcher, final char escape, final
StrMatcher valueDelimiterMatcher) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefixMatcher(prefixMatcher);
+ this.setVariableSuffixMatcher(suffixMatcher);
+ this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(valueDelimiterMatcher);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefixMatcher the prefix for variables, not null
+ * @param suffixMatcher the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiterMatcher the variable default value delimiter
matcher, may be null
+ * @param valueEscapeMatcher the matcher to escape defaulting, may be null.
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final StrLookup variableResolver, final StrMatcher
prefixMatcher,
+ final StrMatcher suffixMatcher, final char escape,
final StrMatcher valueDelimiterMatcher,
+ final StrMatcher valueEscapeMatcher) {
this.setVariableResolver(variableResolver);
this.setVariablePrefixMatcher(prefixMatcher);
this.setVariableSuffixMatcher(suffixMatcher);
this.setEscapeChar(escape);
this.setValueDelimiterMatcher(valueDelimiterMatcher);
+ valueEscapeDelimiterMatcher = valueEscapeMatcher;
+ }
+
+ StrSubstitutor(final StrSubstitutor other) {
+ this.setVariableResolver(other.getVariableResolver());
+ this.setVariablePrefixMatcher(other.getVariablePrefixMatcher());
+ this.setVariableSuffixMatcher(other.getVariableSuffixMatcher());
+ this.setEscapeChar(other.getEscapeChar());
+ this.setValueDelimiterMatcher(other.valueDelimiterMatcher);
+ this.valueEscapeDelimiterMatcher = other.valueEscapeDelimiterMatcher;
+ this.configuration = other.configuration;
+ this.recursiveEvaluationAllowed = other.isRecursiveEvaluationAllowed();
+ this.enableSubstitutionInVariables =
other.isEnableSubstitutionInVariables();
+ this.valueDelimiterString = other.valueDelimiterString;
}
//-----------------------------------------------------------------------
@@ -374,6 +446,19 @@ public class StrSubstitutor {
return StrSubstitutor.replace(source, valueMap);
}
+ private static Map<String, String> toTypeSafeMap(final Properties
properties) {
+ final Map<String, String> map = new HashMap<String,
String>(properties.size());
+ for (final String name : properties.stringPropertyNames()) {
+ map.put(name, properties.getProperty(name));
+ }
+ return map;
+ }
+
+ private static String handleFailedReplacement(String input, Throwable
throwable) {
+ StatusLogger.getLogger().error("Replacement failed on {}", input,
throwable);
+ return input;
+ }
+
//-----------------------------------------------------------------------
/**
* Replaces all the occurrences of variables with their matching values
@@ -399,8 +484,12 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new StringBuilder(source);
- if (!substitute(event, buf, 0, source.length())) {
- return source;
+ try {
+ if (!substitute(event, buf, 0, source.length())) {
+ return source;
+ }
+ } catch (Throwable t) {
+ return handleFailedReplacement(source, t);
}
return buf.toString();
}
@@ -441,8 +530,12 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new StringBuilder(length).append(source,
offset, length);
- if (!substitute(event, buf, 0, length)) {
- return source.substring(offset, offset + length);
+ try {
+ if (!substitute(event, buf, 0, length)) {
+ return source.substring(offset, offset + length);
+ }
+ } catch (Throwable t) {
+ return handleFailedReplacement(source, t);
}
return buf.toString();
}
@@ -475,7 +568,11 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new
StringBuilder(source.length).append(source);
- substitute(event, buf, 0, source.length);
+ try {
+ substitute(event, buf, 0, source.length);
+ } catch (Throwable t) {
+ return handleFailedReplacement(new String(source), t);
+ }
return buf.toString();
}
@@ -517,7 +614,11 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new StringBuilder(length).append(source,
offset, length);
- substitute(event, buf, 0, length);
+ try {
+ substitute(event, buf, 0, length);
+ } catch (Throwable t) {
+ return handleFailedReplacement(new String(source, offset, length),
t);
+ }
return buf.toString();
}
@@ -549,7 +650,11 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new
StringBuilder(source.length()).append(source);
- substitute(event, buf, 0, buf.length());
+ try {
+ substitute(event, buf, 0, buf.length());
+ } catch (Throwable t) {
+ return handleFailedReplacement(source.toString(), t);
+ }
return buf.toString();
}
@@ -591,7 +696,11 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new StringBuilder(length).append(source,
offset, length);
- substitute(event, buf, 0, length);
+ try {
+ substitute(event, buf, 0, length);
+ } catch (Throwable t) {
+ return handleFailedReplacement(source.substring(offset, offset +
length), t);
+ }
return buf.toString();
}
@@ -623,7 +732,11 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new
StringBuilder(source.length()).append(source);
- substitute(event, buf, 0, buf.length());
+ try {
+ substitute(event, buf, 0, buf.length());
+ } catch (Throwable t) {
+ return handleFailedReplacement(source.toString(), t);
+ }
return buf.toString();
}
/**
@@ -664,7 +777,11 @@ public class StrSubstitutor {
return null;
}
final StringBuilder buf = new StringBuilder(length).append(source,
offset, length);
- substitute(event, buf, 0, length);
+ try {
+ substitute(event, buf, 0, length);
+ } catch (Throwable t) {
+ return handleFailedReplacement(source.substring(offset, offset +
length), t);
+ }
return buf.toString();
}
@@ -694,8 +811,13 @@ public class StrSubstitutor {
if (source == null) {
return null;
}
- final StringBuilder buf = new StringBuilder().append(source);
- substitute(event, buf, 0, buf.length());
+ String stringValue = String.valueOf(source);
+ final StringBuilder buf = new
StringBuilder(stringValue.length()).append(stringValue);
+ try {
+ substitute(event, buf, 0, buf.length());
+ } catch (Throwable t) {
+ return handleFailedReplacement(stringValue, t);
+ }
return buf.toString();
}
@@ -753,7 +875,12 @@ public class StrSubstitutor {
return false;
}
final StringBuilder buf = new StringBuilder(length).append(source,
offset, length);
- if (!substitute(event, buf, 0, length)) {
+ try {
+ if (!substitute(event, buf, 0, length)) {
+ return false;
+ }
+ } catch (Throwable t) {
+ StatusLogger.getLogger().error("Replacement failed on {}", source,
t);
return false;
}
source.replace(offset, offset + length, buf.toString());
@@ -868,122 +995,133 @@ public class StrSubstitutor {
final StrMatcher valueDelimiterMatcher = getValueDelimiterMatcher();
final boolean substitutionInVariablesEnabled =
isEnableSubstitutionInVariables();
- final boolean top = (priorVariables == null);
+ final boolean top = priorVariables == null;
boolean altered = false;
int lengthChange = 0;
char[] chars = getChars(buf);
int bufEnd = offset + length;
int pos = offset;
while (pos < bufEnd) {
- final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset,
- bufEnd);
+ final int startMatchLen = prefixMatcher.isMatch(chars, pos,
offset, bufEnd);
if (startMatchLen == 0) {
pos++;
+ } else // found variable start marker
+ if (pos > offset && chars[pos - 1] == escape) {
+ // escaped
+ buf.deleteCharAt(pos - 1);
+ chars = getChars(buf);
+ lengthChange--;
+ altered = true;
+ bufEnd--;
} else {
- // found variable start marker
- if (pos > offset && chars[pos - 1] == escape) {
- // escaped
- buf.deleteCharAt(pos - 1);
- chars = getChars(buf);
- lengthChange--;
- altered = true;
- bufEnd--;
- } else {
- // find suffix
- final int startPos = pos;
- pos += startMatchLen;
- int endMatchLen = 0;
- int nestedVarCount = 0;
- while (pos < bufEnd) {
- if (substitutionInVariablesEnabled
- && (endMatchLen = prefixMatcher.isMatch(chars,
- pos, offset, bufEnd)) != 0) {
- // found a nested variable start
- nestedVarCount++;
- pos += endMatchLen;
- continue;
- }
+ // find suffix
+ final int startPos = pos;
+ pos += startMatchLen;
+ int endMatchLen = 0;
+ int nestedVarCount = 0;
+ while (pos < bufEnd) {
+ if (substitutionInVariablesEnabled
+ && (endMatchLen = prefixMatcher.isMatch(chars,
pos, offset, bufEnd)) != 0) {
+ // found a nested variable start
+ nestedVarCount++;
+ pos += endMatchLen;
+ continue;
+ }
- endMatchLen = suffixMatcher.isMatch(chars, pos, offset,
- bufEnd);
- if (endMatchLen == 0) {
- pos++;
- } else {
- // found variable end marker
- if (nestedVarCount == 0) {
- String varNameExpr = new String(chars, startPos
- + startMatchLen, pos - startPos
- - startMatchLen);
- if (substitutionInVariablesEnabled) {
- final StringBuilder bufName = new
StringBuilder(varNameExpr);
- substitute(event, bufName, 0,
bufName.length());
- varNameExpr = bufName.toString();
+ endMatchLen = suffixMatcher.isMatch(chars, pos, offset,
bufEnd);
+ if (endMatchLen == 0) {
+ pos++;
+ } else {
+ // found variable end marker
+ if (nestedVarCount == 0) {
+ String varNameExpr = new String(chars, startPos +
startMatchLen, pos - startPos - startMatchLen);
+ if (substitutionInVariablesEnabled) {
+ // initialize priorVariables if they're not
already set
+ if (priorVariables == null) {
+ priorVariables = new ArrayList<String>();
}
- pos += endMatchLen;
- final int endPos = pos;
-
- String varName = varNameExpr;
- String varDefaultValue = null;
-
- if (valueDelimiterMatcher != null) {
- final char [] varNameExprChars =
varNameExpr.toCharArray();
- int valueDelimiterMatchLen = 0;
- for (int i = 0; i <
varNameExprChars.length; i++) {
- // if there's any nested variable when
nested variable substitution disabled, then stop resolving name and default
value.
- if (!substitutionInVariablesEnabled
- &&
prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
+ final StringBuilder bufName = new
StringBuilder(varNameExpr);
+ substitute(event, bufName, 0,
bufName.length(), priorVariables);
+ varNameExpr = bufName.toString();
+ }
+ pos += endMatchLen;
+ final int endPos = pos;
+
+ String varName = varNameExpr;
+ String varDefaultValue = null;
+
+ if (valueDelimiterMatcher != null) {
+ final char [] varNameExprChars =
varNameExpr.toCharArray();
+ int valueDelimiterMatchLen = 0;
+ for (int i = 0; i < varNameExprChars.length;
i++) {
+ // if there's any nested variable when
nested variable substitution disabled, then stop resolving name and default
value.
+ if (!substitutionInVariablesEnabled
+ &&
prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
+ break;
+ }
+ if (valueEscapeDelimiterMatcher != null) {
+ int matchLen =
valueEscapeDelimiterMatcher.isMatch(varNameExprChars, i);
+ if (matchLen != 0) {
+ String varNamePrefix =
varNameExpr.substring(0, i) + Interpolator.PREFIX_SEPARATOR;
+ varName = varNamePrefix +
varNameExpr.substring(i + matchLen - 1);
+ for (int j = i + matchLen; j <
varNameExprChars.length; ++j){
+ if ((valueDelimiterMatchLen =
valueDelimiterMatcher.isMatch(varNameExprChars, j)) != 0) {
+ varName = varNamePrefix +
varNameExpr.substring(i + matchLen, j);
+ varDefaultValue =
varNameExpr.substring(j + valueDelimiterMatchLen);
+ break;
+ }
+ }
break;
- }
- if ((valueDelimiterMatchLen =
valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
+ } else if ((valueDelimiterMatchLen =
valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
varName = varNameExpr.substring(0,
i);
varDefaultValue =
varNameExpr.substring(i + valueDelimiterMatchLen);
break;
}
+ } else if ((valueDelimiterMatchLen =
valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
+ varName = varNameExpr.substring(0, i);
+ varDefaultValue =
varNameExpr.substring(i + valueDelimiterMatchLen);
+ break;
}
}
+ }
- // on the first call initialize priorVariables
- if (priorVariables == null) {
- priorVariables = new ArrayList<String>();
- priorVariables.add(new String(chars,
- offset, length + lengthChange));
- }
+ // on the first call initialize priorVariables
+ if (priorVariables == null) {
+ priorVariables = new ArrayList<String>();
+ priorVariables.add(new String(chars, offset,
length + lengthChange));
+ }
- // handle cyclic substitution
- checkCyclicSubstitution(varName,
priorVariables);
- priorVariables.add(varName);
+ // handle cyclic substitution
+ boolean isCyclic = isCyclicSubstitution(varName,
priorVariables);
- // resolve the variable
- String varValue = resolveVariable(event,
varName, buf,
- startPos, endPos);
- if (varValue == null) {
- varValue = varDefaultValue;
- }
- if (varValue != null) {
- // recursive replace
- final int varLen = varValue.length();
- buf.replace(startPos, endPos, varValue);
- altered = true;
- int change = substitute(event, buf,
startPos,
- varLen, priorVariables);
- change = change
- + (varLen - (endPos - startPos));
- pos += change;
- bufEnd += change;
- lengthChange += change;
- chars = getChars(buf); // in case buffer
was
- // altered
- }
+ // resolve the variable
+ String varValue = isCyclic ? null :
resolveVariable(event, varName, buf, startPos, endPos);
+ if (varValue == null) {
+ varValue = varDefaultValue;
+ }
+ if (varValue != null) {
+ // recursive replace
+ final int varLen = varValue.length();
+ buf.replace(startPos, endPos, varValue);
+ altered = true;
+ int change = isRecursiveEvaluationAllowed()
+ ? substitute(event, buf, startPos,
varLen, priorVariables)
+ : 0;
+ change = change + (varLen - (endPos -
startPos));
+ pos += change;
+ bufEnd += change;
+ lengthChange += change;
+ chars = getChars(buf); // in case buffer was
altered
+ }
- // remove variable from the cyclic stack
- priorVariables
- .remove(priorVariables.size() - 1);
- break;
- } else {
- nestedVarCount--;
- pos += endMatchLen;
+ // remove variable from the cyclic stack
+ if (!isCyclic) {
+ priorVariables.remove(priorVariables.size() -
1);
}
+ break;
}
+ nestedVarCount--;
+ pos += endMatchLen;
}
}
}
@@ -995,21 +1133,23 @@ public class StrSubstitutor {
}
/**
- * Checks if the specified variable is already in the stack (list) of
variables.
+ * Checks if the specified variable is already in the stack (list) of
variables, adding the value
+ * if it's not already present.
*
* @param varName the variable name to check
* @param priorVariables the list of prior variables
+ * @return true if this is a cyclic substitution
*/
- private void checkCyclicSubstitution(final String varName, final
List<String> priorVariables) {
+ private boolean isCyclicSubstitution(final String varName, final
List<String> priorVariables) {
if (!priorVariables.contains(varName)) {
- return;
+ priorVariables.add(varName);
+ return false;
}
final StringBuilder buf = new StringBuilder(BUF_SIZE);
buf.append("Infinite loop in property interpolation of ");
- buf.append(priorVariables.remove(0));
- buf.append(": ");
appendWithSeparators(buf, priorVariables, "->");
- throw new IllegalStateException(buf.toString());
+ StatusLogger.getLogger().warn(buf);
+ return true;
}
/**
@@ -1038,7 +1178,12 @@ public class StrSubstitutor {
if (resolver == null) {
return null;
}
- return resolver.lookup(event, variableName);
+ try {
+ return resolver.lookup(event, variableName);
+ } catch (Throwable t) {
+ StatusLogger.getLogger().error("Resolver failed to lookup {}",
variableName, t);
+ return null;
+ }
}
// Escape
@@ -1275,6 +1420,9 @@ public class StrSubstitutor {
setValueDelimiterMatcher(null);
return this;
}
+ String escapeValue = valueDelimiter.substring(0,
valueDelimiter.length() - 1) + "\\"
+ + valueDelimiter.substring(valueDelimiter.length() - 1);
+ valueEscapeDelimiterMatcher = StrMatcher.stringMatcher(escapeValue);
return
setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
}
@@ -1321,6 +1469,14 @@ public class StrSubstitutor {
this.enableSubstitutionInVariables = enableSubstitutionInVariables;
}
+ boolean isRecursiveEvaluationAllowed() {
+ return recursiveEvaluationAllowed;
+ }
+
+ void setRecursiveEvaluationAllowed(final boolean
recursiveEvaluationAllowed) {
+ this.recursiveEvaluationAllowed = recursiveEvaluationAllowed;
+ }
+
private char[] getChars(final StringBuilder sb) {
final char[] chars = new char[sb.length()];
sb.getChars(0, sb.length(), chars, 0);
diff --git
a/log4j-core/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java
b/log4j-core/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java
new file mode 100644
index 0000000..3515e70
--- /dev/null
+++
b/log4j-core/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.junit.InitialLoggerContext;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+public class PatternResolverDoesNotEvaluateThreadContextTest {
+
+
+ private static final String CONFIG =
"log4j2-pattern-layout-with-context.xml";
+ private static final String PARAMETER = "user";
+ private ListAppender listAppender;
+
+ @ClassRule
+ public static InitialLoggerContext context = new
InitialLoggerContext(CONFIG);
+
+ @Before
+ public void before() {
+ listAppender = context.getListAppender("list");
+ listAppender.clear();
+ }
+
+ @Test
+ public void testNoUserSet() {
+ Logger logger = context.getLogger(getClass().getName());
+ logger.info("This is a test");
+ List<String> messages = listAppender.getMessages();
+ assertTrue("No messages returned", messages != null && messages.size()
> 0);
+ String message = messages.get(0);
+ assertEquals("INFO org.apache.logging.log4j.core." +
+ "PatternResolverDoesNotEvaluateThreadContextTest ${ctx:user}
This is a test", message);
+ }
+
+ @Test
+ public void testMessageIsNotLookedUp() {
+ Logger logger = context.getLogger(getClass().getName());
+ logger.info("This is a ${upper:test}");
+ List<String> messages = listAppender.getMessages();
+ assertTrue("No messages returned", messages != null && messages.size()
> 0);
+ String message = messages.get(0);
+ assertEquals("INFO org.apache.logging.log4j.core." +
+ "PatternResolverDoesNotEvaluateThreadContextTest ${ctx:user}
This is a ${upper:test}", message);
+ }
+
+ @Test
+ public void testUser() {
+ Logger logger = context.getLogger(getClass().getName());
+ ThreadContext.put(PARAMETER, "123");
+ try {
+ logger.info("This is a test");
+ } finally {
+ ThreadContext.remove(PARAMETER);
+ }
+ List<String> messages = listAppender.getMessages();
+ assertTrue("No messages returned", messages != null && messages.size()
> 0);
+ String message = messages.get(0);
+ assertEquals("INFO org.apache.logging.log4j.core." +
+ "PatternResolverDoesNotEvaluateThreadContextTest 123 This is a
test", message);
+ }
+
+ @Test
+ public void testUserIsLookup() {
+ Logger logger = context.getLogger(getClass().getName());
+ ThreadContext.put(PARAMETER, "${java:version}");
+ try {
+ logger.info("This is a test");
+ } finally {
+ ThreadContext.remove(PARAMETER);
+ }
+ List<String> messages = listAppender.getMessages();
+ assertTrue("No messages returned", messages != null && messages.size()
> 0);
+ String message = messages.get(0);
+ assertEquals("INFO org.apache.logging.log4j.core." +
+ "PatternResolverDoesNotEvaluateThreadContextTest
${java:version} This is a test", message);
+ }
+
+ @Test
+ public void testUserHasLookup() {
+ Logger logger = context.getLogger(getClass().getName());
+ ThreadContext.put(PARAMETER, "user${java:version}name");
+ try {
+ logger.info("This is a test");
+ } finally {
+ ThreadContext.remove(PARAMETER);
+ }
+ List<String> messages = listAppender.getMessages();
+ assertTrue("No messages returned",messages != null && messages.size()
> 0);
+ String message = messages.get(0);
+ assertEquals("INFO org.apache.logging.log4j.core." +
+ "PatternResolverDoesNotEvaluateThreadContextTest
user${java:version}name This is a test", message);
+ }
+}
diff --git
a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java
new file mode 100644
index 0000000..b7ab1e0
--- /dev/null
+++
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.appender.routing;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.junit.InitialLoggerContext;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class RoutingAppenderKeyLookupEvaluationTest {
+ private static final String CONFIG = "log4j-routing-lookup.xml";
+
+ private static final String KEY = "user";
+ private ListAppender app;
+
+ @Rule
+ public InitialLoggerContext init = new InitialLoggerContext(CONFIG);
+
+ @Before
+ public void setUp() throws Exception {
+ ThreadContext.remove(KEY);
+ this.app = this.init.getListAppender("List");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ this.app.clear();
+ this.init.getContext().stop();
+ ThreadContext.remove(KEY);
+ }
+
+ @Test
+ public void testRoutingNoUser() {
+ Logger logger = init.getLogger(getClass().getName());
+ logger.warn("no user");
+ String message = app.getMessages().get(0);
+ assertEquals("WARN ${ctx:user} no user", message);
+ }
+
+ @Test
+ public void testRoutingDoesNotMatchRoute() {
+ Logger logger = init.getLogger(getClass().getName());
+ ThreadContext.put(KEY, "noRouteExists");
+ logger.warn("unmatched user");
+ assertTrue(app.getMessages().isEmpty());
+ }
+
+ @Test
+ public void testRoutingContainsLookup() {
+ Logger logger = init.getLogger(getClass().getName());
+ ThreadContext.put(KEY, "${java:version}");
+ logger.warn("naughty user");
+ String message = app.getMessages().get(0);
+ assertEquals("WARN ${java:version} naughty user", message);
+ }
+
+ @Test
+ public void testRoutingMatchesEscapedLookup() {
+ Logger logger = init.getLogger(getClass().getName());
+ ThreadContext.put(KEY, "${upper:name}");
+ logger.warn("naughty user");
+ String message = app.getMessages().get(0);
+ assertEquals("WARN ${upper:name} naughty user", message);
+ }
+
+ @Test
+ public void testRoutesThemselvesNotEvaluated() {
+ Logger logger = init.getLogger(getClass().getName());
+ ThreadContext.put(KEY, "NAME");
+ logger.warn("unmatched user");
+ assertTrue(app.getMessages().isEmpty());
+ }
+}
diff --git
a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
index 0f525e9..331f9b0 100644
---
a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
+++
b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
@@ -20,15 +20,13 @@ import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LogEvent;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
-/**
- *
- */
public class StrSubstitutorTest {
private static final String TESTKEY = "TestKey";
@@ -65,4 +63,138 @@ public class StrSubstitutorTest {
value =
subst.replace("${BadKey:-Unknown}-${ctx:BadKey:-}-${sys:BadKey:-Unknown}");
assertEquals("Unknown--Unknown", value);
}
+
+ @Test
+ public void testDefault() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put(TESTKEY, TESTVAL);
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ ThreadContext.put(TESTKEY, TESTVAL);
+ //String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}");
+ final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}");
+ assertEquals("${ctx:TestKey}", value);
+ }
+
+ @Test
+ public void testDefaultReferencesLookupValue() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put(TESTKEY, "${java:version}");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(false);
+ final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}");
+ assertEquals("${ctx:TestKey}", value);
+ }
+
+ @Test
+ public void testInfiniteSubstitutionOnString() {
+ final StrLookup lookup = new Interpolator(new MapLookup(new
HashMap<String, String>()));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(true);
+ String infiniteSubstitution = "${${::-${::-$${::-j}}}}";
+ assertEquals(infiniteSubstitution,
subst.replace(infiniteSubstitution));
+ }
+
+ @Test
+ public void testInfiniteSubstitutionOnStringBuilder() {
+ final StrLookup lookup = new Interpolator(new MapLookup(new
HashMap<String, String>()));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(true);
+ String infiniteSubstitution = "${${::-${::-$${::-j}}}}";
+ assertEquals(infiniteSubstitution, subst.replace(null, new
StringBuilder(infiniteSubstitution)));
+ }
+
+ @Test
+ public void testRecursiveSubstitution() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("first", "${ctx:first}");
+ map.put("second", "secondValue");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(true);
+ assertEquals("${ctx:first} and secondValue",
subst.replace("${ctx:first} and ${ctx:second}"));
+ }
+
+ @Test
+ public void testRecursiveWithDefault() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("first", "${ctx:first:-default}");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(true);
+ assertEquals("default", subst.replace("${ctx:first}"));
+ }
+
+ @Test
+ public void testRecursiveWithRecursiveDefault() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("first", "${ctx:first:-${ctx:first}}");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(true);
+ assertEquals("${ctx:first}", subst.replace("${ctx:first}"));
+ }
+
+ @Test
+ public void testNestedSelfReferenceWithRecursiveEvaluation() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("first", "${${ctx:first}}");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(true);
+ assertEquals("${${ctx:first}}}", subst.replace("${ctx:first}"));
+ }
+
+ @Test
+ public void testRandomWithRecursiveDefault() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("first", "${env:RANDOM:-${ctx:first}}");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(true);
+ assertEquals("${ctx:first}", subst.replace("${ctx:first}"));
+ }
+
+ @Test
+ public void testNoRecursiveEvaluationWithDefault() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("first", "${java:version}");
+ map.put("second", "${java:runtime}");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(false);
+ assertEquals("${java:version}}",
subst.replace("${ctx:first:-${ctx:second}}"));
+ }
+
+ @Test
+ public void testNoRecursiveEvaluationWithDepthOne() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("first", "${java:version}");
+ final StrLookup lookup = new Interpolator(new MapLookup(map));
+ final StrSubstitutor subst = new StrSubstitutor(lookup);
+ subst.setRecursiveEvaluationAllowed(false);
+ assertEquals("${java:version}", subst.replace("${ctx:first}"));
+ }
+
+ @Test
+ public void testLookupThrows() {
+ final StrSubstitutor subst = new StrSubstitutor(new Interpolator(new
StrLookup() {
+
+ @Override
+ public String lookup(String key) {
+ if ("throw".equals(key)) {
+ throw new RuntimeException();
+ }
+ return "success";
+ }
+
+ @Override
+ public String lookup(LogEvent event, String key) {
+ return lookup(key);
+ }
+ }));
+ subst.setRecursiveEvaluationAllowed(false);
+ assertEquals("success ${foo:throw} success", subst.replace("${foo:a}
${foo:throw} ${foo:c}"));
+ }
}
diff --git a/log4j-core/src/test/resources/log4j-routing.xml
b/log4j-core/src/test/resources/log4j-routing-lookup.xml
similarity index 51%
copy from log4j-core/src/test/resources/log4j-routing.xml
copy to log4j-core/src/test/resources/log4j-routing-lookup.xml
index 4d83886..33b8ec8 100644
--- a/log4j-core/src/test/resources/log4j-routing.xml
+++ b/log4j-core/src/test/resources/log4j-routing-lookup.xml
@@ -16,43 +16,23 @@
limitations under the License.
-->
-<Configuration status="OFF" name="RoutingTest">
- <Properties>
- <Property
name="filename">target/routing1/routingtest-$${sd:type}.log</Property>
- </Properties>
- <ThresholdFilter level="debug"/>
-
+<Configuration status="OFF" name="RoutingAppenderKeyLookupEvaluationTest">
<Appenders>
- <Console name="STDOUT">
- <PatternLayout pattern="%m%n"/>
- </Console>
<List name="List">
- <ThresholdFilter level="debug"/>
+ <PatternLayout pattern="%p $${ctx:user} %m"/>
</List>
<Routing name="Routing">
- <Routes pattern="$${sd:type}">
- <Route>
- <RollingFile name="Routing-${sd:type}" fileName="${filename}"
-
filePattern="target/routing1/test1-${sd:type}.%i.log.gz">
- <PatternLayout>
- <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
- </PatternLayout>
- <SizeBasedTriggeringPolicy size="500" />
- </RollingFile>
- </Route>
- <Route ref="STDOUT" key="Audit"/>
- <Route ref="List" key="Service"/>
+ <Routes pattern="$${ctx:user:-none}">
+ <Route ref="List" key="$${upper:name}"/>
+ <Route ref="List" key="none"/>
+ <Route ref="List" key="$${java:version}"/>
</Routes>
</Routing>
</Appenders>
<Loggers>
- <Logger name="EventLogger" level="info" additivity="false">
+ <Root level="debug">
<AppenderRef ref="Routing"/>
- </Logger>
-
- <Root level="error">
- <AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
diff --git a/log4j-core/src/test/resources/log4j-routing.json
b/log4j-core/src/test/resources/log4j-routing.json
index 809b3c2..4322d8d 100644
--- a/log4j-core/src/test/resources/log4j-routing.json
+++ b/log4j-core/src/test/resources/log4j-routing.json
@@ -15,9 +15,6 @@
* limitations under the license.
*/
{ "configuration": { "status": "error", "name": "RoutingTest",
- "properties": {
- "property": { "name": "filename", "value" :
"target/rolling1/rollingtest-$${sd:type}.log" }
- },
"ThresholdFilter": { "level": "debug" },
"appenders": {
"Console": { "name": "STDOUT",
@@ -31,7 +28,7 @@
"Route": [
{
"RollingFile": {
- "name": "Rolling-${sd:type}", "fileName": "${filename}",
+ "name": "Rolling-${sd:type}", "fileName":
"target/rolling1/rollingtest-${sd:type}.log",
"filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
"PatternLayout": {"pattern": "%d %p %C{1.} [%t] %m%n"},
"SizeBasedTriggeringPolicy": { "size": "500" }
diff --git a/log4j-core/src/test/resources/log4j-routing.xml
b/log4j-core/src/test/resources/log4j-routing.xml
index 4d83886..003f7d7 100644
--- a/log4j-core/src/test/resources/log4j-routing.xml
+++ b/log4j-core/src/test/resources/log4j-routing.xml
@@ -17,9 +17,6 @@
-->
<Configuration status="OFF" name="RoutingTest">
- <Properties>
- <Property
name="filename">target/routing1/routingtest-$${sd:type}.log</Property>
- </Properties>
<ThresholdFilter level="debug"/>
<Appenders>
@@ -32,7 +29,7 @@
<Routing name="Routing">
<Routes pattern="$${sd:type}">
<Route>
- <RollingFile name="Routing-${sd:type}" fileName="${filename}"
+ <RollingFile name="Routing-${sd:type}"
fileName="target/routing1/routingtest-${sd:type}.log"
filePattern="target/routing1/test1-${sd:type}.%i.log.gz">
<PatternLayout>
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
diff --git a/log4j-core/src/test/resources/log4j-routing2.json
b/log4j-core/src/test/resources/log4j-routing2.json
index fe50b37..af67454 100644
--- a/log4j-core/src/test/resources/log4j-routing2.json
+++ b/log4j-core/src/test/resources/log4j-routing2.json
@@ -15,9 +15,6 @@
* limitations under the license.
*/
{ "configuration": { "status": "error", "name": "RoutingTest",
- "properties": {
- "property": { "name": "filename", "value" :
"target/rolling1/rollingtest-$${sd:type}.log" }
- },
"ThresholdFilter": { "level": "debug" },
"appenders": {
"appender": [
@@ -28,7 +25,7 @@
"Route": [
{
"RollingFile": {
- "name": "Rolling-${sd:type}", "fileName": "${filename}",
+ "name": "Rolling-${sd:type}", "fileName":
"target/rolling1/rollingtest-${sd:type}.log",
"filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
"PatternLayout": {"pattern": "%d %p %C{1.} [%t] %m%n"},
"SizeBasedTriggeringPolicy": { "size": "500" }
diff --git
a/log4j-core/src/test/resources/log4j2-pattern-layout-with-context.xml
b/log4j-core/src/test/resources/log4j2-pattern-layout-with-context.xml
new file mode 100644
index 0000000..e644d26
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j2-pattern-layout-with-context.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+
+-->
+<Configuration status="DEBUG" packages="">
+ <Properties>
+ <Property name="pattern">%p %c $${ctx:user} %m</Property>
+ </Properties>
+ <Appenders>
+ <List name="list">
+ <PatternLayout pattern="${pattern}"/>
+ </List>
+ </Appenders>
+
+ <Loggers>
+ <Root level="INFO">
+ <AppenderRef ref="list"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git
a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
index 8a67ce2..8eda0a4 100644
---
a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
+++
b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
@@ -29,6 +29,7 @@ import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.impl.ContextAnchor;
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.selector.ContextSelector;
@@ -58,7 +59,7 @@ final class Log4jWebInitializerImpl extends AbstractLifeCycle
implements Log4jWe
}
private final Map<String, String> map = new ConcurrentHashMap<String,
String>();
- private final StrSubstitutor substitutor = new StrSubstitutor(new
Interpolator(map));
+ private final StrSubstitutor substitutor = new
ConfigurationStrSubstitutor(new Interpolator(map));
private final ServletContext servletContext;
private String name;
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7d6508e..3905ae3 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -24,6 +24,9 @@
</properties>
<body>
<release version="2.3.1" date="2021-12-xx" description="GA Release 2.3.1">
+ <action issue="LOG4J2-3230" dev="ckozak" type="fix">
+ Fix string substitution recursion.
+ </action>
<action issue="LOG4J2-3198" dev="rgoers" type="add">
Pattern layout no longer enables lookups within message text.
</action>