Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 {} {}";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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="WARN" name="RegexFilterPatternFlagsTest">
<Filters>
<RegexFilter regex="error" patternFlags="CASE_INSENSITIVE" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<Loggers>
<Root level="INFO"/>
</Loggers>
</Configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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.
*
Expand All @@ -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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a breaking API change to me. That is, I guess the following was working, and is broken with this change:

<RegexFilter regex="error" onMatch="ACCEPT" onMismatch="DENY">
  <PatternFlags>
    <tag-name-here-does-not-matter>UNIX_LINES</tag-name-here-does-not-matter>
    <tag-name-here-does-not-matter>DOTALL</tag-name-here-does-not-matter>
  </PatternFlags>
</RegexFilter>

@ramanathan1504, would you mind verifying this, please?

@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<Field> 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<Builder>
implements org.apache.logging.log4j.core.util.Builder<RegexFilter> {

@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/changelog/.2.x.x/regexfilter_patternflags_builder.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="fixed">
<issue id="3239" link="https://github.com/apache/logging-log4j2/issues/3239"/>
<issue id="4119" link="https://github.com/apache/logging-log4j2/pull/4119"/>
<description format="asciidoc">
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.
</description>
</entry>

6 changes: 6 additions & 0 deletions src/site/antora/modules/ROOT/pages/manual/filters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,12 @@ Besides the <<common-configuration-attributes,common configuration attributes>>,
| `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]
Expand Down