diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java
index 671d998258b..bf7cd93796f 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java
@@ -19,6 +19,7 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -27,6 +28,9 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Filter.Result;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.SimpleMessage;
@@ -91,6 +95,18 @@ void testNoMsg() throws Exception {
assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, null, (Object[]) null));
}
+ @Test
+ void testPatternFlagsFromConfiguration() {
+ try (final LoggerContext context =
+ Configurator.initialize("RegexFilterPatternFlagsTest", "filter/RegexFilterPatternFlagsTest.xml")) {
+ final Configuration configuration = context.getConfiguration();
+ final Filter filter = configuration.getFilter();
+ assertNotNull(filter);
+ assertThat(filter.filter(null, null, null, (Object) "ERROR", null), equalTo(Result.ACCEPT));
+ assertThat(filter.filter(null, null, null, (Object) "warn", null), equalTo(Result.DENY));
+ }
+ }
+
@Test
void testParameterizedMsg() throws Exception {
final String msg = "params {} {}";
diff --git a/log4j-core-test/src/test/resources/filter/RegexFilterPatternFlagsTest.xml b/log4j-core-test/src/test/resources/filter/RegexFilterPatternFlagsTest.xml
new file mode 100644
index 00000000000..66d79c8e0c8
--- /dev/null
+++ b/log4j-core-test/src/test/resources/filter/RegexFilterPatternFlagsTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
index 62d41b31f59..fba195831f3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
@@ -16,9 +16,7 @@
*/
package org.apache.logging.log4j.core.filter;
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.Comparator;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.Level;
@@ -29,13 +27,15 @@
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFormatMessage;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.message.StringFormattedMessage;
import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.apache.logging.log4j.util.Strings;
/**
* A filter that matches the given regular expression pattern against messages.
@@ -116,6 +116,11 @@ public String toString() {
return sb.toString();
}
+ @PluginBuilderFactory
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
/**
* Creates a Filter that matches a regular expression.
*
@@ -130,48 +135,100 @@ public String toString() {
* @param mismatch
* The action to perform when a mismatch occurs.
* @return The RegexFilter.
- * @throws IllegalAccessException When there is no access to the definition of the specified member.
- * @throws IllegalArgumentException When passed an illegal or inappropriate argument.
*/
- // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
- @PluginFactory
public static RegexFilter createFilter(
// @formatter:off
@PluginAttribute("regex") final String regex,
- @PluginElement("PatternFlags") final String[] patternFlags,
+ @PluginAttribute("patternFlags") final String[] patternFlags,
@PluginAttribute("useRawMsg") final Boolean useRawMsg,
@PluginAttribute("onMatch") final Result match,
@PluginAttribute("onMismatch") final Result mismatch)
- // @formatter:on
- throws IllegalArgumentException, IllegalAccessException {
- if (regex == null) {
- LOGGER.error("A regular expression must be provided for RegexFilter");
- return null;
+ // @formatter:on
+ {
+ final String flags = patternFlags == null ? null : String.join(",", patternFlags);
+ return RegexFilter.newBuilder()
+ .setRegex(regex)
+ .setPatternFlags(flags)
+ .setUseRawMsg(Boolean.TRUE.equals(useRawMsg))
+ .setOnMatch(match)
+ .setOnMismatch(mismatch)
+ .build();
+ }
+
+ private static int toPatternFlags(final String patternFlags) {
+ if (Strings.isBlank(patternFlags)) {
+ return DEFAULT_PATTERN_FLAGS;
}
- return new RegexFilter(
- Boolean.TRUE.equals(useRawMsg), Pattern.compile(regex, toPatternFlags(patternFlags)), match, mismatch);
+ int flags = DEFAULT_PATTERN_FLAGS;
+ for (final String flagName : Strings.splitList(patternFlags)) {
+ flags |= toPatternFlag(flagName);
+ }
+ return flags;
}
- private static int toPatternFlags(final String[] patternFlags)
- throws IllegalArgumentException, IllegalAccessException {
- if (patternFlags == null || patternFlags.length == 0) {
+ private static int toPatternFlag(final String flagName) {
+ if (Strings.isBlank(flagName)) {
return DEFAULT_PATTERN_FLAGS;
}
- final Field[] fields = Pattern.class.getDeclaredFields();
- final Comparator comparator = (f1, f2) -> f1.getName().compareTo(f2.getName());
- Arrays.sort(fields, comparator);
- final String[] fieldNames = new String[fields.length];
- for (int i = 0; i < fields.length; i++) {
- fieldNames[i] = fields[i].getName();
+ switch (flagName.trim().toUpperCase(Locale.ROOT)) {
+ case "UNIX_LINES":
+ return Pattern.UNIX_LINES;
+ case "CASE_INSENSITIVE":
+ return Pattern.CASE_INSENSITIVE;
+ case "COMMENTS":
+ return Pattern.COMMENTS;
+ case "MULTILINE":
+ return Pattern.MULTILINE;
+ case "LITERAL":
+ return Pattern.LITERAL;
+ case "DOTALL":
+ return Pattern.DOTALL;
+ case "UNICODE_CASE":
+ return Pattern.UNICODE_CASE;
+ case "CANON_EQ":
+ return Pattern.CANON_EQ;
+ case "UNICODE_CHARACTER_CLASS":
+ return Pattern.UNICODE_CHARACTER_CLASS;
+ default:
+ return DEFAULT_PATTERN_FLAGS;
}
- int flags = DEFAULT_PATTERN_FLAGS;
- for (final String test : patternFlags) {
- final int index = Arrays.binarySearch(fieldNames, test);
- if (index >= 0) {
- final Field field = fields[index];
- flags |= field.getInt(Pattern.class);
+ }
+
+ public static class Builder extends AbstractFilterBuilder
+ implements org.apache.logging.log4j.core.util.Builder {
+
+ @PluginBuilderAttribute
+ @Required(message = "A regular expression must be provided for RegexFilter")
+ private String regex;
+
+ @PluginBuilderAttribute
+ private String patternFlags;
+
+ @PluginBuilderAttribute("useRawMsg")
+ private boolean useRawMsg;
+
+ public Builder setRegex(final String regex) {
+ this.regex = regex;
+ return this;
+ }
+
+ public Builder setPatternFlags(final String patternFlags) {
+ this.patternFlags = patternFlags;
+ return this;
+ }
+
+ public Builder setUseRawMsg(final boolean useRawMsg) {
+ this.useRawMsg = useRawMsg;
+ return this;
+ }
+
+ @Override
+ public RegexFilter build() {
+ if (!isValid()) {
+ return null;
}
+ return new RegexFilter(
+ useRawMsg, Pattern.compile(regex, toPatternFlags(patternFlags)), getOnMatch(), getOnMismatch());
}
- return flags;
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java
index 38173ef4735..f02feaa580e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java
@@ -22,7 +22,7 @@
* {@link org.apache.logging.log4j.core.Filter#ELEMENT_TYPE filter}.
*/
@Export
-@Version("2.25.3")
+@Version("2.26.0")
package org.apache.logging.log4j.core.filter;
import org.osgi.annotation.bundle.Export;
diff --git a/src/changelog/.2.x.x/regexfilter_patternflags_builder.xml b/src/changelog/.2.x.x/regexfilter_patternflags_builder.xml
new file mode 100644
index 00000000000..8e4d97dc706
--- /dev/null
+++ b/src/changelog/.2.x.x/regexfilter_patternflags_builder.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Refactor `RegexFilter` to a plugin builder, restore configurable regex `patternFlags` via a CSV attribute, and avoid `useRawMsg` null-unboxing pitfalls by using a primitive boolean in the builder path.
+
+
+
diff --git a/src/site/antora/modules/ROOT/pages/manual/filters.adoc b/src/site/antora/modules/ROOT/pages/manual/filters.adoc
index f5ecc6f6f36..fc1de7fd434 100644
--- a/src/site/antora/modules/ROOT/pages/manual/filters.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/filters.adoc
@@ -767,6 +767,12 @@ Besides the <>,
| `boolean`
| `false`
| If `true`, for xref:manual/messages.adoc#ParameterizedMessage[`ParameterizedMessage`], xref:manual/messages.adoc#StringFormattedMessage[`StringFormattedMessage`], and xref:manual/messages.adoc#MessageFormatMessage[`MessageFormatMessage`], the message format pattern; for xref:manual/messages.adoc#StructuredDataMessage[`StructuredDataMessage`], the message field will be used as the match target.
+
+| patternFlags
+| `String`
+|
+| Comma-separated list of https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/regex/Pattern.html[`Pattern`] flag names.
+Supported values are `UNIX_LINES`, `CASE_INSENSITIVE`, `COMMENTS`, `MULTILINE`, `LITERAL`, `DOTALL`, `UNICODE_CASE`, `CANON_EQ`, and `UNICODE_CHARACTER_CLASS`.
|===
[WARNING]