Author: rgoers
Date: Tue Jun 10 03:12:24 2014
New Revision: 1601546
URL: http://svn.apache.org/r1601546
Log:
LOG4J2-419 - Support default value for missing key in look ups with fallbacking
to looking in the properties map.
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-loggerprops.xml
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-routing-by-jndi.xml
logging/log4j/log4j2/trunk/src/changes/changes.xml
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java?rev=1601546&r1=1601545&r2=1601546&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
Tue Jun 10 03:12:24 2014
@@ -63,6 +63,26 @@ import org.apache.logging.log4j.util.Str
* The quick brown fox jumped over the lazy dog.
* </pre>
* <p>
+ * Also, this class allows to set a default value for unresolved variables.
+ * The default value for a variable can be appended to the variable name after
the variable
+ * default value delimiter. The default value of the variable default value
delimiter is ':-',
+ * as in bash and other *nix shells, as those are arguably where the default
${} delimiter set originated.
+ * The variable default value delimiter can be manually set by calling {@link
#setValueDelimiterMatcher(StrMatcher)},
+ * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
+ * The following shows an example with varialbe default value settings:
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target}.
${undefined.number:-1234567890}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ * yielding:
+ * <pre>
+ * The quick brown fox jumped over the lazy dog. 1234567890.
+ * </pre>
+ * <p>
* In addition to this usage pattern there are some static convenience methods
that
* cover the most common use cases. These methods can be used without the need
of
* manually creating an instance. However if multiple replace operations are
to be
@@ -116,6 +136,10 @@ public class StrSubstitutor {
* 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(":-");
private static final int BUF_SIZE = 256;
@@ -132,6 +156,10 @@ public class StrSubstitutor {
*/
private StrMatcher suffixMatcher;
/**
+ * Stores the default variable value delimiter
+ */
+ private StrMatcher valueDelimiterMatcher;
+ /**
* Variable resolution is delegated to an implementer of VariableResolver.
*/
private StrLookup variableResolver;
@@ -187,6 +215,21 @@ public class StrSubstitutor {
/**
* Creates a new instance and initializes it.
*
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final Map<String, String> valueMap, final String
prefix, final String suffix,
+ final char escape, final String valueDelimiter) {
+ this(new MapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
* @param variableResolver the variable resolver, may be null
*/
public StrSubstitutor(final StrLookup variableResolver) {
@@ -214,6 +257,24 @@ public class StrSubstitutor {
* Creates a new instance and initializes it.
*
* @param variableResolver the variable resolver, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter string, may
be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final StrLookup variableResolver, final String
prefix, final String suffix, final char escape, final String valueDelimiter) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefix(prefix);
+ this.setVariableSuffix(suffix);
+ this.setEscapeChar(escape);
+ this.setValueDelimiter(valueDelimiter);
+ }
+
+ /**
+ * 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
@@ -222,11 +283,28 @@ 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);
+ }
+
+ /**
+ * 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
+ * @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) {
this.setVariableResolver(variableResolver);
this.setVariablePrefixMatcher(prefixMatcher);
this.setVariableSuffixMatcher(suffixMatcher);
this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(valueDelimiterMatcher);
}
+
//-----------------------------------------------------------------------
/**
* Replaces all the occurrences of variables in the given source object
with
@@ -756,6 +834,8 @@ public class StrSubstitutor {
final StrMatcher prefixMatcher = getVariablePrefixMatcher();
final StrMatcher suffixMatcher = getVariableSuffixMatcher();
final char escape = getEscapeChar();
+ final StrMatcher valueDelimiterMatcher = getValueDelimiterMatcher();
+ final boolean substitutionInVariablesEnabled =
isEnableSubstitutionInVariables();
final boolean top = (priorVariables == null);
boolean altered = false;
@@ -784,7 +864,7 @@ public class StrSubstitutor {
int endMatchLen = 0;
int nestedVarCount = 0;
while (pos < bufEnd) {
- if (isEnableSubstitutionInVariables()
+ if (substitutionInVariablesEnabled
&& (endMatchLen = prefixMatcher.isMatch(chars,
pos, offset, bufEnd)) != 0) {
// found a nested variable start
@@ -800,17 +880,37 @@ public class StrSubstitutor {
} else {
// found variable end marker
if (nestedVarCount == 0) {
- String varName = new String(chars, startPos
+ String varNameExpr = new String(chars, startPos
+ startMatchLen, pos - startPos
- startMatchLen);
- if (isEnableSubstitutionInVariables()) {
- final StringBuilder bufName = new
StringBuilder(varName);
+ if (substitutionInVariablesEnabled) {
+ final StringBuilder bufName = new
StringBuilder(varNameExpr);
substitute(event, bufName, 0,
bufName.length());
- varName = bufName.toString();
+ 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 ((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>();
@@ -823,8 +923,11 @@ public class StrSubstitutor {
priorVariables.add(varName);
// resolve the variable
- final String varValue = resolveVariable(event,
varName, buf,
+ String varValue = resolveVariable(event,
varName, buf,
startPos, endPos);
+ if (varValue == null) {
+ varValue = varDefaultValue;
+ }
if (varValue != null) {
// recursive replace
final int varLen = varValue.length();
@@ -1057,6 +1160,76 @@ public class StrSubstitutor {
return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
}
+ // Variable Default Value Delimiter
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the variable default value delimiter matcher currently in use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that
delimite the
+ * variable name and the variable default value. This delimiter is
expressed in terms of a matcher
+ * allowing advanced variable default value delimiter matches.
+ * <p>
+ * If it returns null, then the variable default value resolution is
disabled.
+ *
+ * @return the variable default value delimiter matcher in use, may be null
+ */
+ public StrMatcher getValueDelimiterMatcher() {
+ return valueDelimiterMatcher;
+ }
+
+ /**
+ * Sets the variable default value delimiter matcher to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that
delimite the
+ * variable name and the variable default value. This delimiter is
expressed in terms of a matcher
+ * allowing advanced variable default value delimiter matches.
+ * <p>
+ * If the <code>valueDelimiterMatcher</code> is null, then the variable
default value resolution
+ * becomes disabled.
+ *
+ * @param valueDelimiterMatcher variable default value delimiter matcher
to use, may be null
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setValueDelimiterMatcher(final StrMatcher
valueDelimiterMatcher) {
+ this.valueDelimiterMatcher = valueDelimiterMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that
delimite the
+ * variable name and the variable default value. This method allows a
single character
+ * variable default value delimiter to be easily set.
+ *
+ * @param valueDelimiter the variable default value delimiter character
to use
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
+ return
setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that
delimite the
+ * variable name and the variable default value. This method allows a
string
+ * variable default value delimiter to be easily set.
+ * <p>
+ * If the <code>valueDelimiter</code> is null or empty string, then the
variable default
+ * value resolution becomes disabled.
+ *
+ * @param valueDelimiter the variable default value delimiter string to
use, may be null or empty
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
+ if (Strings.isEmpty(valueDelimiter)) {
+ setValueDelimiterMatcher(null);
+ return this;
+ }
+ return
setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
+ }
+
// Resolver
//-----------------------------------------------------------------------
/**
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java?rev=1601546&r1=1601545&r2=1601546&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java
Tue Jun 10 03:12:24 2014
@@ -65,7 +65,7 @@ public class RoutingAppenderWithJndiTest
// default route when there's no jndi resource
StructuredDataMessage msg = new StructuredDataMessage("Test", "This is
a message from unknown context", "Context");
EventLogger.logEvent(msg);
- File defaultLogFile = new
File("target/routingbyjndi/routingbyjnditest-default.log");
+ File defaultLogFile = new
File("target/routingbyjndi/routingbyjnditest-unknown.log");
assertTrue("The default log file was not created",
defaultLogFile.exists());
// now set jndi resource to Application1
@@ -91,5 +91,21 @@ public class RoutingAppenderWithJndiTest
assertNotNull("No events generated", listAppender2.getEvents());
assertTrue("Incorrect number of events. Expected 2, got " +
listAppender2.getEvents().size(), listAppender2.getEvents().size() == 2);
assertTrue("Incorrect number of events. Expected 1, got " +
listAppender1.getEvents().size(), listAppender1.getEvents().size() == 1);
+
+ // now set jndi resource to Application3.
+ // The context name, 'Application3', will be used as log file name by
the default route.
+ context.rebind("java:comp/env/logging/context-name", "Application3");
+ msg = new StructuredDataMessage("Test", "This is a message from
Application3", "Context");
+ EventLogger.logEvent(msg);
+ File application3LogFile = new
File("target/routingbyjndi/routingbyjnditest-Application3.log");
+ assertTrue("The Application3 log file was not created",
application3LogFile.exists());
+
+ // now set jndi resource to Application4
+ // The context name, 'Application4', will be used as log file name by
the default route.
+ context.rebind("java:comp/env/logging/context-name", "Application4");
+ msg = new StructuredDataMessage("Test", "This is a message from
Application4", "Context");
+ EventLogger.logEvent(msg);
+ File application4LogFile = new
File("target/routingbyjndi/routingbyjnditest-Application4.log");
+ assertTrue("The Application3 log file was not created",
application4LogFile.exists());
}
}
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java?rev=1601546&r1=1601545&r2=1601546&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java
Tue Jun 10 03:12:24 2014
@@ -17,11 +17,12 @@
package org.apache.logging.log4j.core.config.xml;
import java.util.List;
-import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
@@ -72,7 +73,16 @@ public class XmlLoggerPropsTest {
final List<String> events = listAppender.getMessages();
assertTrue("No events", events.size() > 0);
assertTrue("Incorrect number of events", events.size() == 2);
+ assertTrue("Incorrect value", events.get(0).contains("user="));
+ assertTrue("Incorrect value",
events.get(0).contains("phrasex=****"));
assertTrue("Incorrect value", events.get(0).contains("test=test"));
+ assertTrue("Incorrect value",
events.get(0).contains("test2=test2default"));
+ assertTrue("Incorrect value",
events.get(0).contains("test3=Unknown"));
+ assertTrue("Incorrect value", events.get(1).contains("user="));
+ assertTrue("Incorrect value",
events.get(1).contains("phrasex=****"));
+ assertTrue("Incorrect value", events.get(1).contains("test=test"));
+ assertTrue("Incorrect value",
events.get(1).contains("test2=test2default"));
+ assertTrue("Incorrect value",
events.get(1).contains("test3=Unknown"));
} finally {
System.clearProperty("test");
}
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java?rev=1601546&r1=1601545&r2=1601546&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
Tue Jun 10 03:12:24 2014
@@ -31,7 +31,7 @@ import static org.junit.Assert.*;
*/
public class StrSubstitutorTest {
- private static final String TESTKEY = "TestKey";
+ private static final String TESTKEY = "TestKey";
private static final String TESTVAL = "TestValue";
@@ -57,5 +57,12 @@ public class StrSubstitutorTest {
assertEquals("TestValue-TestValue-TestValue", value);
value = subst.replace("${BadKey}");
assertEquals("${BadKey}", value);
+
+ value =
subst.replace("${BadKey:-Unknown}-${ctx:BadKey:-Unknown}-${sys:BadKey:-Unknown}");
+ assertEquals("Unknown-Unknown-Unknown", value);
+ value =
subst.replace("${BadKey:-Unknown}-${ctx:BadKey}-${sys:BadKey:-Unknown}");
+ assertEquals("Unknown-${ctx:BadKey}-Unknown", value);
+ value =
subst.replace("${BadKey:-Unknown}-${ctx:BadKey:-}-${sys:BadKey:-Unknown}");
+ assertEquals("Unknown--Unknown", value);
}
}
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-loggerprops.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-loggerprops.xml?rev=1601546&r1=1601545&r2=1601546&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-loggerprops.xml
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-loggerprops.xml
Tue Jun 10 03:12:24 2014
@@ -16,20 +16,25 @@
~ limitations under the License.
-->
<Configuration status="OFF" strict="false" name="DSI">
+ <Properties>
+ <Property name="test2">test2default</Property>
+ </Properties>
<Appenders>
<List name="List">
- <PatternLayout pattern="[%-5level] %c{1.} user=%X{user} test=%X{test}
%msg%n" />
+ <PatternLayout pattern="[%-5level] %c{1.} user=%X{user}
phrasex=%X{phrasex} test=%X{test} test2=$${sys:test2}
test3=$${sys:test3:-Unknown} %msg%n" />
</List>
</Appenders>
<Loggers>
<Logger name="org.apache.logging.log4j.core" level="debug"
additivity="false">
<Property name="user">$${sys:user.name}</Property>
+ <Property name="phrasex">${sys:user.phrasex:-****}</Property>
<Property name="test">${sys:test}</Property>
<AppenderRef ref="List"/>
</Logger>
<Root level="debug">
<Property name="user">${sys:user.name}</Property>
+ <Property name="phrasex">${sys:user.phrasex:-****}</Property>
<Property name="test">${sys:test}</Property>
<AppenderRef ref="List" />
</Root>
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-routing-by-jndi.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-routing-by-jndi.xml?rev=1601546&r1=1601545&r2=1601546&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-routing-by-jndi.xml
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/test/resources/log4j-routing-by-jndi.xml
Tue Jun 10 03:12:24 2014
@@ -32,9 +32,8 @@
</List>
<Routing name="Routing">
<Routes pattern="$${jndi:logging/context-name}">
- <!-- Default route -->
<Route>
- <File name="DefaultApplicationLogFile"
fileName="target/routingbyjndi/routingbyjnditest-default.log" append="false">
+ <File name="DefaultApplicationLogFile"
fileName="target/routingbyjndi/routingbyjnditest-${jndi:logging/context-name:-unknown}.log"
append="false">
<PatternLayout>
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</PatternLayout>
Modified: logging/log4j/log4j2/trunk/src/changes/changes.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/changes/changes.xml?rev=1601546&r1=1601545&r2=1601546&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/changes/changes.xml (original)
+++ logging/log4j/log4j2/trunk/src/changes/changes.xml Tue Jun 10 03:12:24 2014
@@ -22,6 +22,9 @@
</properties>
<body>
<release version="2.0-rc2" date="2014-MM-DD" description="Bug fixes and
enhancements">
+ <action issue="LOG4J2-419" dev="rgoers" type="update" due-to="Woonsan
Ko">
+ Support default value for missing key in look ups with fallbacking
to looking in the properties map.
+ </action>
<action issue="LOG4J2-563" dev="rgoers" type="fix" due-to="Michael
Friedmann">
FlumeAvroManager now always uses a client type of default_failover.
</action>