From f0ec0b8ba757e7e137ef91762ac8764801d16eec Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Wed, 12 Feb 2025 14:07:22 +0100 Subject: [PATCH 01/42] Removed 'patternFlags' @PluginAttribute from RegexFilter @PluginFactory createFilter. (#3086) --- .../log4j/core/filter/RegexFilter.java | 77 +++++++++++-------- ...remove_patternflags_from_PluginFactory.xml | 10 +++ 2 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml 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..84cdbfad47b 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,6 @@ */ package org.apache.logging.log4j.core.filter; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.logging.log4j.Level; @@ -29,7 +26,6 @@ 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.message.Message; import org.apache.logging.log4j.message.MessageFormatMessage; @@ -43,7 +39,6 @@ @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) public final class RegexFilter extends AbstractFilter { - private static final int DEFAULT_PATTERN_FLAGS = 0; private final Pattern pattern; private final boolean useRawMessage; @@ -110,10 +105,7 @@ private Result filter(final String msg) { @Override public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append("useRaw=").append(useRawMessage); - sb.append(", pattern=").append(pattern.toString()); - return sb.toString(); + return "useRaw=" + useRawMessage + ", pattern=" + pattern.toString(); } /** @@ -123,6 +115,40 @@ public String toString() { * The regular expression to match. * @param patternFlags * An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag. + * (no longer used - pattern flags can be embedded in regex-expression. + * @param useRawMsg + * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. + * @param match + * The action to perform when a match occurs. + * @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. + * @deprecated use {@link #createFilter(String, Boolean, Result, Result)} + */ + @Deprecated + // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder + public static RegexFilter createFilter( + // @formatter:off + @PluginAttribute("regex") final String regex, + final String[] patternFlags, + @PluginAttribute("useRawMsg") final Boolean useRawMsg, + @PluginAttribute("onMatch") final Result match, + @PluginAttribute("onMismatch") final Result mismatch) + // @formatter:on + throws IllegalArgumentException, IllegalAccessException { + + // LOG4J-3086 - pattern-flags can be embedded in RegEx expression + + return createFilter(regex, useRawMsg, match, mismatch); + } + + /** + * Creates a Filter that matches a regular expression. + * + * @param regex + * The regular expression to match. * @param useRawMsg * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. * @param match @@ -138,40 +164,23 @@ public String toString() { public static RegexFilter createFilter( // @formatter:off @PluginAttribute("regex") final String regex, - @PluginElement("PatternFlags") final String[] patternFlags, @PluginAttribute("useRawMsg") final Boolean useRawMsg, @PluginAttribute("onMatch") final Result match, @PluginAttribute("onMismatch") final Result mismatch) // @formatter:on throws IllegalArgumentException, IllegalAccessException { + boolean raw = Boolean.TRUE.equals(useRawMsg); if (regex == null) { LOGGER.error("A regular expression must be provided for RegexFilter"); return null; } - return new RegexFilter( - Boolean.TRUE.equals(useRawMsg), Pattern.compile(regex, toPatternFlags(patternFlags)), match, mismatch); - } - - private static int toPatternFlags(final String[] patternFlags) - throws IllegalArgumentException, IllegalAccessException { - if (patternFlags == null || patternFlags.length == 0) { - 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(); - } - 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); - } + final Pattern pattern; + try { + pattern = Pattern.compile(regex); + } catch (final Exception ex) { + LOGGER.error("Unable to compile regular expression: {}", regex, ex); + return null; } - return flags; + return new RegexFilter(raw, pattern, match, mismatch); } } diff --git a/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml b/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml new file mode 100644 index 00000000000..0e61653f85c --- /dev/null +++ b/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml @@ -0,0 +1,10 @@ + + + + + Removed 'patternFlags' @PluginAttribute from RegexFilter @PluginFactory createFilter. + + From 8c0e3c6c4f32ba97985efc05286e5425bfe36742 Mon Sep 17 00:00:00 2001 From: Suvrat Acharya <140749446+Suvrat1629@users.noreply.github.com> Date: Sun, 16 Feb 2025 13:59:16 +0530 Subject: [PATCH 02/42] Improve configuration error handling of HttpAppender (#3438) This PR introduces improvements to `HttpAppender` and adds a new test class, `HttpAppenderBuilderTest`, to enhance test coverage. The changes include: * Updating `HttpAppender` to improve validating behavior. * Adding HttpAppenderBuilderTest.java to verify the builder logic for HttpAppender. Ensuring that missing configurations (e.g., URL, Layout) correctly log errors. Co-authored-by: Piotr P. Karwasz --- .../appender/HttpAppenderBuilderTest.java | 130 ++++++++++++++++++ .../log4j/core/appender/HttpAppender.java | 24 +++- .../.2.x.x/3011_http_appender_validation.xml | 8 ++ 3 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderBuilderTest.java create mode 100644 src/changelog/.2.x.x/3011_http_appender_validation.xml diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderBuilderTest.java new file mode 100644 index 00000000000..2680961d19c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderBuilderTest.java @@ -0,0 +1,130 @@ +/* + * 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; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.MalformedURLException; +import java.net.URL; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.layout.JsonLayout; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +class HttpAppenderBuilderTest { + + private HttpAppender.Builder getBuilder() { + Configuration mockConfig = new DefaultConfiguration(); + return HttpAppender.newBuilder().setConfiguration(mockConfig).setName("TestHttpAppender"); // Name is required + } + + @Test + @UsingStatusListener + void testBuilderWithoutUrl(final ListStatusListener listener) throws Exception { + HttpAppender appender = HttpAppender.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setName("TestAppender") + .setLayout(JsonLayout.createDefaultLayout()) // Providing a layout here + .build(); + + assertThat(listener.findStatusData(Level.ERROR)) + .anyMatch(statusData -> + statusData.getMessage().getFormattedMessage().contains("HttpAppender requires URL to be set.")); + } + + @Test + @UsingStatusListener + void testBuilderWithUrlAndWithoutLayout(final ListStatusListener listener) throws Exception { + HttpAppender appender = HttpAppender.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setName("TestAppender") + .setUrl(new URL("http://localhost:8080/logs")) + .build(); + + assertThat(listener.findStatusData(Level.ERROR)).anyMatch(statusData -> statusData + .getMessage() + .getFormattedMessage() + .contains("HttpAppender requires a layout to be set.")); + } + + @Test + void testBuilderWithValidConfiguration() throws Exception { + URL url = new URL("http://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + + HttpAppender.Builder builder = getBuilder().setUrl(url).setLayout(layout); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with valid configuration."); + } + + @Test + void testBuilderWithCustomMethod() throws Exception { + URL url = new URL("http://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + String customMethod = "PUT"; + + HttpAppender.Builder builder = + getBuilder().setUrl(url).setLayout(layout).setMethod(customMethod); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with a custom HTTP method."); + } + + @Test + void testBuilderWithHeaders() throws Exception { + URL url = new URL("http://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + Property[] headers = new Property[] { + Property.createProperty("Header1", "Value1"), Property.createProperty("Header2", "Value2") + }; + + HttpAppender.Builder builder = + getBuilder().setUrl(url).setLayout(layout).setHeaders(headers); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with headers."); + } + + @Test + void testBuilderWithSslConfiguration() throws Exception { + URL url = new URL("https://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + + // Use real SslConfiguration instead of Mockito mock + SslConfiguration sslConfig = SslConfiguration.createSSLConfiguration(null, null, null, false); + + HttpAppender.Builder builder = + getBuilder().setUrl(url).setLayout(layout).setSslConfiguration(sslConfig); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with SSL configuration."); + } + + @Test + void testBuilderWithInvalidUrl() { + assertThrows(MalformedURLException.class, () -> new URL("invalid-url")); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java index b24e165b3e1..56d1ffb5e37 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java @@ -32,16 +32,15 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.status.StatusLogger; -/** - * Sends log events over HTTP. - */ @Plugin(name = "Http", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) public final class HttpAppender extends AbstractAppender { + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + /** * Builds HttpAppender instances. - * @param The type to build */ public static class Builder> extends AbstractAppender.Builder implements org.apache.logging.log4j.core.util.Builder { @@ -70,6 +69,18 @@ public static class Builder> extends AbstractAppender.Build @Override public HttpAppender build() { + // Validate URL presence + if (url == null) { + LOGGER.error("HttpAppender requires URL to be set."); + return null; // Return null if URL is missing + } + + // Validate layout presence + if (getLayout() == null) { + LOGGER.error("HttpAppender requires a layout to be set."); + return null; // Return null if layout is missing + } + final HttpManager httpManager = new HttpURLConnectionManager( getConfiguration(), getConfiguration().getLoggerContext(), @@ -81,10 +92,12 @@ public HttpAppender build() { headers, sslConfiguration, verifyHostname); + return new HttpAppender( getName(), getLayout(), getFilter(), isIgnoreExceptions(), httpManager, getPropertyArray()); } + // Getter and Setter methods public URL getUrl() { return url; } @@ -149,9 +162,6 @@ public B setVerifyHostname(final boolean verifyHostname) { } } - /** - * @return a builder for a HttpAppender. - */ @PluginBuilderFactory public static > B newBuilder() { return new Builder().asBuilder(); diff --git a/src/changelog/.2.x.x/3011_http_appender_validation.xml b/src/changelog/.2.x.x/3011_http_appender_validation.xml new file mode 100644 index 00000000000..5038a378d17 --- /dev/null +++ b/src/changelog/.2.x.x/3011_http_appender_validation.xml @@ -0,0 +1,8 @@ + + + + Improves validation of HTTP Appender. + From dd7b6d30c612212b9f3959e26fce0637222cacb5 Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Mon, 17 Feb 2025 17:05:26 +0100 Subject: [PATCH 03/42] Updates per PR Code Review (#3086) + made AbstractFiltter.AbstractFilterBuilder onMatch/onMismatch fields protected + added AbstractFilter(AbstractFilterBuilder) constructor + added RegexFilter.Builder implementation + added RegexFilter(Builder) constructor + moved RegexFilter Pattern compile into constructor + added fields to persist configuration propertties + getters (regexExpression, patternFlags) + changed private constructor to accept builder as argument + renamed private method 'targetMessageTest' to more approprriate 'getMessageTextByType' + added Javadoc + grouped deprecations --- .../log4j/core/filter/RegexFilterTest.java | 11 +- .../log4j/core/filter/AbstractFilter.java | 33 +- .../log4j/core/filter/RegexFilter.java | 358 ++++++++++++++---- .../logging/log4j/core/util/Builder.java | 4 + ...remove_patternflags_from_PluginFactory.xml | 0 5 files changed, 328 insertions(+), 78 deletions(-) rename src/changelog/{2.25.0 => .2.x.x}/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml (100%) 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..e5801342dd6 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; @@ -43,13 +44,17 @@ static void before() { @Test void testRegexFilterDoesNotThrowWithAllTheParametersExceptRegexEqualNull() { assertDoesNotThrow(() -> { - RegexFilter.createFilter(".* test .*", null, null, null, null); + RegexFilter.newBuilder().setRegex(".* test .*").build(); }); } @Test void testThresholds() throws Exception { - RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null); + RegexFilter filter = RegexFilter.newBuilder() + .setRegex(".* test .*") + .setUseRawMsg(false) + .build(); + assertNotNull(filter); filter.start(); assertTrue(filter.isStarted()); assertSame( @@ -65,7 +70,7 @@ void testThresholds() throws Exception { .setMessage(new SimpleMessage("test")) // .build(); assertSame(Filter.Result.DENY, filter.filter(event)); - filter = RegexFilter.createFilter(null, null, false, null, null); + filter = RegexFilter.newBuilder().build(); assertNull(filter); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java index 397390bcbc3..05b1e6b275b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.core.filter; +import java.util.Objects; +import java.util.Optional; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.AbstractLifeCycle; @@ -43,16 +45,30 @@ public abstract static class AbstractFilterBuilder builder) { + + Objects.requireNonNull(builder, "The 'builder' argument cannot be null."); + + this.onMatch = Optional.ofNullable(builder.onMatch).orElse(Result.NEUTRAL); + this.onMismatch = Optional.ofNullable(builder.onMismatch).orElse(Result.DENY); + } + @Override protected boolean equalsImpl(final Object obj) { if (this == obj) { 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 84cdbfad47b..d26d157bcc7 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,7 +16,10 @@ */ package org.apache.logging.log4j.core.filter; -import java.util.regex.Matcher; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; import java.util.regex.Pattern; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -26,7 +29,9 @@ 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.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.PluginElement; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFormatMessage; import org.apache.logging.log4j.message.ParameterizedMessage; @@ -39,53 +44,132 @@ @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) public final class RegexFilter extends AbstractFilter { + /** The regular-expression. */ + private final String regex; + + /** The pattern compiled from the regular-expression. */ private final Pattern pattern; + + /** Flag: if {@code true} use message format-pattern / field for the match target. */ private final boolean useRawMessage; - private RegexFilter(final boolean raw, final Pattern pattern, final Result onMatch, final Result onMismatch) { - super(onMatch, onMismatch); - this.pattern = pattern; - this.useRawMessage = raw; + /** + * Constructs a new {@code RegexFilter} configured by the given builder. + * @param builder the builder + * @throws IllegalArgumentException if the regular expression cannot be compiled to a pattern + */ + private RegexFilter(final Builder builder) { + + super(builder); + + this.regex = builder.regex; + this.useRawMessage = Boolean.TRUE.equals(builder.useRawMsg); + + try { + this.pattern = Pattern.compile(regex); + } catch (final Exception ex) { + throw new IllegalArgumentException("Unable to compile regular expression: '" + regex + "'.", ex); + } + } + + /** + * Returns the regular-expression. + * @return the regular-expression (it may be an empty string but never {@code null}) + */ + public String getRegex() { + return this.regex; + } + + /** + * Returns the compiled regular-expression pattern. + * @return the pattern (will never be {@code null} + */ + public Pattern getPattern() { + return this.pattern; + } + + /** + * Returns whether the raw-message should be used. + * @return {@code} if the raw message should be used; otherwise, {@code false} + */ + public boolean isUseRawMessage() { + return this.useRawMessage; } + /** {@inheritDoc} */ @Override public Result filter( final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { - if (useRawMessage || params == null || params.length == 0) { - return filter(msg); - } - return filter(ParameterizedMessage.format(msg, params)); + return (useRawMessage || params == null || params.length == 0) + ? filter(msg) + : filter(ParameterizedMessage.format(msg, params)); } + /** {@inheritDoc} */ @Override public Result filter( final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) { - if (msg == null) { - return onMismatch; - } - return filter(msg.toString()); + return (msg == null) ? this.onMismatch : filter(msg.toString()); } + /** {@inheritDoc} */ @Override public Result filter( final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) { if (msg == null) { return onMismatch; } - final String text = targetMessageTest(msg); - return filter(text); + return filter(getMessageTextByType(msg)); } + /** {@inheritDoc} */ @Override public Result filter(final LogEvent event) { - final String text = targetMessageTest(event.getMessage()); - return filter(text); + return filter(getMessageTextByType(event.getMessage())); + } + + /** + * Apply the filter to the given message and return the match/mismatch result. + *

+ * If the given '{@code msg}' is {@code null} the configured mismatch result will be returned. + *

+ * @param msg the message + * @return the filter result + */ + private Result filter(final String msg) { + if (msg == null) { + return onMismatch; + } + return pattern.matcher(msg).matches() ? onMatch : onMismatch; } - // While `Message#getFormat()` is broken in general, it still makes sense for certain types. - // Hence, suppress the deprecation warning. + /** + * Tests the filter pattern against the given Log4j {@code Message}. + *

+ * If the raw-message flag is enabled and message is an instance of the following, the raw message format + * will be returned. + *

+ *
    + *
  • {@link MessageFormatMessage}
  • + *
  • {@link ParameterizedMessage}
  • + *
  • {@link StringFormattedMessage}
  • + *
  • {@link StructuredDataMessage}
  • + *
+ *

+ * If the '{@code useRawMessage}' flag is disabled OR the message is not one of the above + * implementations, the message's formatted message will be returned. + *

+ *

Developer Note

+ *

+ * While `Message#getFormat()` is broken in general, it still makes sense for certain types. + * Hence, suppress the deprecation warning. + *

+ * + * @param message the message + * @return the target message based on configuration and message-type + */ @SuppressWarnings("deprecation") - private String targetMessageTest(final Message message) { + private String getMessageTextByType(final Message message) { return useRawMessage && (message instanceof ParameterizedMessage || message instanceof StringFormattedMessage @@ -95,92 +179,220 @@ private String targetMessageTest(final Message message) { : message.getFormattedMessage(); } - private Result filter(final String msg) { - if (msg == null) { - return onMismatch; - } - final Matcher m = pattern.matcher(msg); - return m.matches() ? onMatch : onMismatch; - } - @Override public String toString() { - return "useRaw=" + useRawMessage + ", pattern=" + pattern.toString(); + return "useRawMessage=" + useRawMessage + ", regex=" + regex + ", pattern=" + pattern.toString(); } /** - * Creates a Filter that matches a regular expression. + * Creates a new builder instance. * - * @param regex - * The regular expression to match. - * @param patternFlags - * An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag. - * (no longer used - pattern flags can be embedded in regex-expression. - * @param useRawMsg - * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. - * @param match - * The action to perform when a match occurs. - * @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. - * @deprecated use {@link #createFilter(String, Boolean, Result, Result)} + * @return the new builder instance + */ + @PluginBuilderFactory + public static RegexFilter.Builder newBuilder() { + return new RegexFilter.Builder(); + } + + /** + * A {@link RegexFilter} builder instance. + */ + public static final class Builder extends AbstractFilterBuilder + implements org.apache.logging.log4j.core.util.Builder { + + /* NOTE: LOG4J-3086 - No patternFlags in builder - this functionality has been deprecated/removed. */ + + /** + * The regular expression to match. + */ + @PluginBuilderAttribute + private String regex; + + /** + * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, + * and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, + * the message field will be used as the match target. + */ + @PluginBuilderAttribute + private Boolean useRawMsg; + + /** + * Private constructor. + */ + private Builder() { + super(); + } + + /** + * Sets the regular-expression. + * + * @param regex the regular-expression + * @return this builder + */ + public Builder setRegex(final String regex) { + this.regex = regex; + return this; + } + + /** + * Sets the use raw msg flag. + * + * @param useRawMsg {@code true} if the message format-patter/field will be used as match target; + * otherwise, {@code false} + * @return this builder + */ + public Builder setUseRawMsg(final boolean useRawMsg) { + this.useRawMsg = useRawMsg; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isValid() { + return (regex != null); + } + + /** + * Builds and returns a {@link RegexFilter} instance configured by this builder. + * + * @return the created {@link RegexFilter} or {@code null} if the builder is misconfigured + */ + @Override + public RegexFilter build() { + + if (!isValid()) { + return null; + } + + try { + return new RegexFilter(this); + } catch (final Exception ex) { + LOGGER.error("Unable to create RegexFilter. {}", ex.getMessage(), ex); + return null; + } + } + } + + /* + * DEPRECATIONS: + * The constructor/fields/methods below have been deprecated. + * - the 'create***' factory methods should no longer be used - use the builder instead + * - pattern-flags should now be passed via the regular expression itself + */ + + /** + * @deprecated pattern flags have been deprecated - they can just be included in the regex-expression. */ @Deprecated - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - public static RegexFilter createFilter( - // @formatter:off - @PluginAttribute("regex") final String regex, - final String[] patternFlags, - @PluginAttribute("useRawMsg") final Boolean useRawMsg, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) - // @formatter:on - throws IllegalArgumentException, IllegalAccessException { + private static final int DEFAULT_PATTERN_FLAGS = 0; - // LOG4J-3086 - pattern-flags can be embedded in RegEx expression + /** + * @deprecated - pattern flags no longer supported. + */ + @Deprecated + private String[] patternFlags = new String[0]; - return createFilter(regex, useRawMsg, match, mismatch); + /** + * @deprecated use {@link RegexFilter.Builder} instead + */ + @Deprecated + @SuppressWarnings("MagicConstant") + private RegexFilter( + final boolean useRawMessage, + final String regex, + final String[] patternFlags, + final Result onMatch, + final Result onMismatch) { + super(onMatch, onMismatch); + this.regex = Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter"); + this.patternFlags = patternFlags == null ? new String[0] : patternFlags.clone(); + try { + int flags = toPatternFlags(this.patternFlags); + this.pattern = Pattern.compile(regex, flags); + } catch (final Exception ex) { + throw new IllegalArgumentException("Unable to compile regular expression: '" + regex + "'.", ex); + } + this.useRawMessage = useRawMessage; + } + + /** + * Returns the pattern-flags applied to the regular-expression when compiling the pattern. + * + * @return the pattern-flags (maybe empty but never {@code null} + * @deprecated pattern-flags are no longer supported + */ + @Deprecated + public String[] getPatternFlags() { + return this.patternFlags.clone(); } /** * Creates a Filter that matches a regular expression. * - * @param regex - * The regular expression to match. - * @param useRawMsg - * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. - * @param match - * The action to perform when a match occurs. - * @param mismatch - * The action to perform when a mismatch occurs. + * @param regex The regular expression to match. + * @param patternFlags An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag. + * (no longer used - pattern flags can be embedded in regex-expression. + * @param useRawMsg If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, + * and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, + * the message field will be used as the match target. + * @param match The action to perform when a match occurs. + * @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 IllegalAccessException When there is no access to the definition of the specified member. * @throws IllegalArgumentException When passed an illegal or inappropriate argument. + * @deprecated use {@link #newBuilder} to instantiate builder */ - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - @PluginFactory + @Deprecated public static RegexFilter createFilter( // @formatter:off @PluginAttribute("regex") final String regex, + @PluginElement("PatternFlags") final String[] patternFlags, @PluginAttribute("useRawMsg") final Boolean useRawMsg, @PluginAttribute("onMatch") final Result match, @PluginAttribute("onMismatch") final Result mismatch) // @formatter:on throws IllegalArgumentException, IllegalAccessException { + + // LOG4J-3086 - pattern-flags can be embedded in RegEx expression + boolean raw = Boolean.TRUE.equals(useRawMsg); if (regex == null) { LOGGER.error("A regular expression must be provided for RegexFilter"); return null; } - final Pattern pattern; + try { - pattern = Pattern.compile(regex); + return new RegexFilter(raw, regex, patternFlags, match, mismatch); } catch (final Exception ex) { - LOGGER.error("Unable to compile regular expression: {}", regex, ex); + LOGGER.error("Unable to create RegexFilter. {}", ex.getMessage(), ex); return null; } - return new RegexFilter(raw, pattern, match, mismatch); + } + + /** @deprecated pattern flags have been deprecated - they can just be included in the regex-expression. */ + @Deprecated + private static int toPatternFlags(final String[] patternFlags) + throws IllegalArgumentException, IllegalAccessException { + if (patternFlags == null || patternFlags.length == 0) { + 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(); + } + 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); + } + } + return flags; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java index 10bc1a9f52e..8a772dc7330 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java @@ -44,6 +44,10 @@ public interface Builder { */ T build(); + /** + * Validates that the builder is properly configured to build. + * @return {@code true} if the builder configuration is valid; otherwise, {@code false} + */ default boolean isValid() { return PluginBuilder.validateFields(this, getErrorPrefix()); } diff --git a/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml b/src/changelog/.2.x.x/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml similarity index 100% rename from src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml rename to src/changelog/.2.x.x/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml From 29e472b15ee366925a4292c7676be2482b1787cd Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Mon, 17 Feb 2025 17:47:17 +0100 Subject: [PATCH 04/42] A few more improvements + added tests (#3086) --- .../log4j/core/filter/RegexFilterTest.java | 126 +++++++++++++++--- .../log4j/core/filter/RegexFilter.java | 42 ++++-- 2 files changed, 142 insertions(+), 26 deletions(-) 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 e5801342dd6..6cb07a5f7df 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,9 +19,12 @@ 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.logging.log4j.Level; @@ -87,9 +90,18 @@ void testDotAllPattern() throws Exception { } @Test - void testNoMsg() throws Exception { - final RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null); + void testNoMsg() { + + final RegexFilter filter = + RegexFilter.newBuilder() + .setRegex(".* test .*") + .setUseRawMsg(false) + .build(); + + assertNotNull(filter); + filter.start(); + assertTrue(filter.isStarted()); assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null)); assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Message) null, null)); @@ -97,28 +109,112 @@ void testNoMsg() throws Exception { } @Test - void testParameterizedMsg() throws Exception { + void testParameterizedMsg() { final String msg = "params {} {}"; final Object[] params = {"foo", "bar"}; // match against raw message - final RegexFilter rawFilter = RegexFilter.createFilter( - "params \\{\\} \\{\\}", - null, - true, // useRawMsg - Result.ACCEPT, - Result.DENY); + final RegexFilter rawFilter = + RegexFilter.newBuilder() + .setRegex("params \\{\\} \\{\\}") + .setUseRawMsg(true) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY) + .build(); + + assertNotNull(rawFilter); + final Result rawResult = rawFilter.filter(null, null, null, msg, params); assertThat(rawResult, equalTo(Result.ACCEPT)); // match against formatted message - final RegexFilter fmtFilter = RegexFilter.createFilter( - "params foo bar", - null, - false, // useRawMsg - Result.ACCEPT, - Result.DENY); + final RegexFilter fmtFilter = + RegexFilter.newBuilder() + .setRegex("params foo bar") + .setUseRawMsg(false) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY).build(); + + assertNotNull(fmtFilter); + final Result fmtResult = fmtFilter.filter(null, null, null, msg, params); assertThat(fmtResult, equalTo(Result.ACCEPT)); } + + /** + * A builder with no 'regex' expression should both be invalid and return null on 'build()'. + */ + @Test + void testWithValidRegex() { + + final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores + + final RegexFilter.Builder builder = + RegexFilter.newBuilder().setRegex(regex).setUseRawMsg(false).setOnMatch(Result.ACCEPT).setOnMismatch(Result.DENY); + + assertTrue(builder.isValid()); + + final RegexFilter filter = builder.build(); + + assertNotNull(filter); + + assertEquals(Result.ACCEPT, filter.filter("Hello_123")); + + assertEquals(Result.DENY, filter.filter("Hello@123")); + + assertEquals(regex, filter.getRegex()); + } + + @Test + void testRegexFilterGetters() { + + final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores + + final RegexFilter filter = + RegexFilter.newBuilder() + .setRegex(regex) + .setUseRawMsg(false) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY) + .build(); + + assertNotNull(filter); + + assertEquals(regex, filter.getRegex()); + assertFalse(filter.isUseRawMessage()); + assertEquals(Result.ACCEPT, filter.getOnMatch()); + assertEquals(Result.DENY, filter.getOnMismatch()); + assertNotNull(filter.getPattern()); + assertEquals(regex, filter.getPattern().pattern()); + } + + /** + * A builder with no 'regex' expression should both be invalid and return null on 'build()'. + */ + @Test + void testBuilderWithoutRegexNotValid() { + + final RegexFilter.Builder builder = RegexFilter.newBuilder(); + + assertFalse(builder.isValid()); + + assertNull(builder.build()); + + } + + /** + * A builder with an invalid 'regex' expression should return null on 'build()'. + */ + @Test + void testBuilderWithInvalidRegexNotValid() { + + final RegexFilter.Builder builder = RegexFilter.newBuilder(); + + builder.setRegex("[a-z"); + + assertFalse(builder.isValid()); + + assertNull(builder.build()); + + } } 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 d26d157bcc7..6c842e66593 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 @@ -21,6 +21,7 @@ import java.util.Comparator; import java.util.Objects; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; @@ -44,9 +45,6 @@ @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) public final class RegexFilter extends AbstractFilter { - /** The regular-expression. */ - private final String regex; - /** The pattern compiled from the regular-expression. */ private final Pattern pattern; @@ -62,13 +60,12 @@ private RegexFilter(final Builder builder) { super(builder); - this.regex = builder.regex; this.useRawMessage = Boolean.TRUE.equals(builder.useRawMsg); try { - this.pattern = Pattern.compile(regex); + this.pattern = Pattern.compile(builder.regex); } catch (final Exception ex) { - throw new IllegalArgumentException("Unable to compile regular expression: '" + regex + "'.", ex); + throw new IllegalArgumentException("Unable to compile regular expression: '" + builder.regex + "'.", ex); } } @@ -77,7 +74,7 @@ private RegexFilter(final Builder builder) { * @return the regular-expression (it may be an empty string but never {@code null}) */ public String getRegex() { - return this.regex; + return this.pattern.pattern(); } /** @@ -136,7 +133,7 @@ public Result filter(final LogEvent event) { * @param msg the message * @return the filter result */ - private Result filter(final String msg) { + public Result filter(final String msg) { if (msg == null) { return onMismatch; } @@ -181,7 +178,7 @@ private String getMessageTextByType(final Message message) { @Override public String toString() { - return "useRawMessage=" + useRawMessage + ", regex=" + regex + ", pattern=" + pattern.toString(); + return "useRawMessage=" + useRawMessage + ", pattern=" + pattern.toString(); } /** @@ -251,7 +248,11 @@ public Builder setUseRawMsg(final boolean useRawMsg) { */ @Override public boolean isValid() { - return (regex != null); + boolean valid = true; + if (!isRegexValid()) { + valid = false; + } + return valid; } /** @@ -273,6 +274,25 @@ public RegexFilter build() { return null; } } + + /** + * Validates the 'regex' attribute. + *

+ * If the regular-expression is not set, or cannot be compiled to a valid pattern the validation will fail. + *

+ * @return {@code true} if the regular-expression is valid; otherwise, {@code false} + */ + private boolean isRegexValid() { + if (regex == null) { + return false; + } + try { + Pattern.compile(regex); + } catch (final PatternSyntaxException ex) { + return false; + } + return true; + } } /* @@ -306,7 +326,7 @@ private RegexFilter( final Result onMatch, final Result onMismatch) { super(onMatch, onMismatch); - this.regex = Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter"); + Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter"); this.patternFlags = patternFlags == null ? new String[0] : patternFlags.clone(); try { int flags = toPatternFlags(this.patternFlags); From 85c6c9b8cf18c1458e3abb5ff60bf233f4acf85b Mon Sep 17 00:00:00 2001 From: Clay Johnson Date: Tue, 18 Feb 2025 13:58:49 -0600 Subject: [PATCH 05/42] Publish build scans to develocity.apache.org (#3396) * Publish build scans to develocity.apache.org * Use `DEVELOCITY_ACCESS_KEY` to authenticate to `develocity.apache.org` --- .github/workflows/build.yaml | 2 +- .github/workflows/develocity-publish-build-scans.yaml | 4 ++-- .github/workflows/merge-dependabot.yaml | 2 +- .mvn/develocity.xml | 2 +- pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ac7d6128ffc..bf3be134349 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,7 +32,7 @@ jobs: if: github.actor != 'dependabot[bot]' uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.3.0 secrets: - DV_ACCESS_TOKEN: ${{ startsWith(github.ref_name, 'release/') && '' || secrets.GE_ACCESS_TOKEN }} + DV_ACCESS_TOKEN: ${{ startsWith(github.ref_name, 'release/') && '' || secrets.DEVELOCITY_ACCESS_KEY }} with: java-version: | 8 diff --git a/.github/workflows/develocity-publish-build-scans.yaml b/.github/workflows/develocity-publish-build-scans.yaml index 78069943bb0..31fa075ac2f 100644 --- a/.github/workflows/develocity-publish-build-scans.yaml +++ b/.github/workflows/develocity-publish-build-scans.yaml @@ -38,5 +38,5 @@ jobs: - name: Publish Build Scans uses: gradle/develocity-actions/maven-publish-build-scan@b8d3a572314ffff3b940a2c1b7b384d4983d422d # 1.3 with: - develocity-url: 'https://ge.apache.org' - develocity-access-key: ${{ secrets.GE_ACCESS_TOKEN }} + develocity-url: 'https://develocity.apache.org' + develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} diff --git a/.github/workflows/merge-dependabot.yaml b/.github/workflows/merge-dependabot.yaml index 4e35def4b67..c81076e1860 100644 --- a/.github/workflows/merge-dependabot.yaml +++ b/.github/workflows/merge-dependabot.yaml @@ -32,7 +32,7 @@ jobs: if: github.repository == 'apache/logging-log4j2' && github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]' uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.3.0 secrets: - DV_ACCESS_TOKEN: ${{ secrets.GE_ACCESS_TOKEN }} + DV_ACCESS_TOKEN: ${{ secrets.DEVELOCITY_ACCESS_KEY }} with: java-version: | 8 diff --git a/.mvn/develocity.xml b/.mvn/develocity.xml index 84def1dec34..caa112766a0 100644 --- a/.mvn/develocity.xml +++ b/.mvn/develocity.xml @@ -2,7 +2,7 @@ logging-log4j2 - https://ge.apache.org + https://develocity.apache.org diff --git a/pom.xml b/pom.xml index bcc6f1b9c75..41283036545 100644 --- a/pom.xml +++ b/pom.xml @@ -370,7 +370,7 @@ ${maven.multiModuleProjectDirectory}/target/plugin-descriptors/phase1 ${maven.multiModuleProjectDirectory}/target/plugin-descriptors/phase2 - + 3.2.5 From fef8af8e66981bf32933b2158cafeabc3305c2d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Tue, 18 Feb 2025 22:06:41 +0100 Subject: [PATCH 06/42] Fix null termination advice for SOA and JTL --- .../antora/modules/ROOT/examples/cloud/logstash/log4j2.json | 4 +--- .../modules/ROOT/examples/cloud/logstash/log4j2.properties | 1 - .../antora/modules/ROOT/examples/cloud/logstash/log4j2.xml | 2 +- .../antora/modules/ROOT/examples/cloud/logstash/log4j2.yaml | 3 +-- src/site/antora/modules/ROOT/pages/soa.adoc | 3 +-- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.json b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.json index 14512de6346..3ebdb0994eb 100644 --- a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.json +++ b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.json @@ -6,9 +6,7 @@ "name": "SOCKET", "host": "localhost", "port": 12345, - "JsonTemplateLayout": { - "nullEventDelimiterEnabled": true - } + "JsonTemplateLayout": {} } // end::socketAppender[] }, diff --git a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.properties b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.properties index b3aa633b9f2..c25e660b87a 100644 --- a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.properties +++ b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.properties @@ -21,7 +21,6 @@ appender.0.name = SOCKET appender.0.host = localhost appender.0.port = 12345 appender.0.layout.type = JsonTemplateLayout -appender.0.layout.nullEventDelimiterEnabled = true # end::socketAppender[] rootLogger.level = WARN diff --git a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.xml b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.xml index bc7e6e3697e..13b960ec2b9 100644 --- a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.xml +++ b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.xml @@ -24,7 +24,7 @@ - + diff --git a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.yaml b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.yaml index 60cc47d586d..d6e0a44787c 100644 --- a/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.yaml +++ b/src/site/antora/modules/ROOT/examples/cloud/logstash/log4j2.yaml @@ -22,8 +22,7 @@ Configuration: name: "SOCKET" host: "localhost" port: 12345 - JsonTemplateLayout: - nullEventDelimiterEnabled: true + JsonTemplateLayout: {} # end::socketAppender[] Loggers: diff --git a/src/site/antora/modules/ROOT/pages/soa.adoc b/src/site/antora/modules/ROOT/pages/soa.adoc index 5db72c82478..063909ae495 100644 --- a/src/site/antora/modules/ROOT/pages/soa.adoc +++ b/src/site/antora/modules/ROOT/pages/soa.adoc @@ -99,8 +99,7 @@ Nevertheless, there are some tips we recommend you to practice: * *For writing to console*, use a xref:manual/appenders.adoc#ConsoleAppender[Console Appender] and make sure to configure its `direct` attribute to `true` for the maximum efficiency. -* *For writing to an external service*, use a xref:manual/appenders/network.adoc#SocketAppender[Socket Appender] and make sure to set the protocol to TCP and configure the null delimiter of the associated layout. -For instance, see xref:manual/json-template-layout.adoc#plugin-attr-nullEventDelimiterEnabled[the `nullEventDelimiterEnabled` configuration attribute of JSON Template Layout]. +* *For writing to an external service*, use a xref:manual/appenders/network.adoc#SocketAppender[Socket Appender] and make sure to configure the protocol and layout's null termination (e.g., see xref:manual/json-template-layout.adoc#plugin-attr-nullEventDelimiterEnabled[the `nullEventDelimiterEnabled` configuration attribute of JSON Template Layout]) appropriately. [#file] === Avoid writing to files From 55b799bbafd03503fa20dfcfa5c1ff7ad63fa360 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:36:24 +0100 Subject: [PATCH 07/42] Bump org.apache.logging:logging-parent from 11.3.0 to 12.0.0 in /log4j-parent (#3452) * Bump org.apache.logging:logging-parent in /log4j-parent Bumps [org.apache.logging:logging-parent](https://github.com/apache/logging-parent) from 11.3.0 to 12.0.0. - [Release notes](https://github.com/apache/logging-parent/releases) - [Commits](https://github.com/apache/logging-parent/compare/rel/11.3.0...rel/12.0.0) --- updated-dependencies: - dependency-name: org.apache.logging:logging-parent dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Necessary fixes for `12.0.0` upgrade. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Piotr P. Karwasz --- .github/workflows/build.yaml | 6 ++-- .github/workflows/codeql-analysis.yaml | 2 +- .github/workflows/deploy-site.yaml | 6 ++-- .github/workflows/merge-dependabot.yaml | 4 +-- log4j-parent/pom.xml | 28 +++++++++++++++++-- pom.xml | 4 ++- ...date_org_apache_logging_logging_parent.xml | 2 +- 7 files changed, 39 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bf3be134349..e4046edb7c3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -30,7 +30,7 @@ jobs: build: if: github.actor != 'dependabot[bot]' - uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/12.0.0 secrets: DV_ACCESS_TOKEN: ${{ startsWith(github.ref_name, 'release/') && '' || secrets.DEVELOCITY_ACCESS_KEY }} with: @@ -44,7 +44,7 @@ jobs: deploy-snapshot: needs: build if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x' - uses: apache/logging-parent/.github/workflows/deploy-snapshot-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-snapshot-reusable.yaml@rel/12.0.0 # Secrets for deployments secrets: NEXUS_USERNAME: ${{ secrets.NEXUS_USER }} @@ -57,7 +57,7 @@ jobs: deploy-release: needs: build if: github.repository == 'apache/logging-log4j2' && startsWith(github.ref_name, 'release/') - uses: apache/logging-parent/.github/workflows/deploy-release-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-release-reusable.yaml@rel/12.0.0 # Secrets for deployments secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml index cc2dbcaaf85..05a37e02f99 100644 --- a/.github/workflows/codeql-analysis.yaml +++ b/.github/workflows/codeql-analysis.yaml @@ -30,7 +30,7 @@ permissions: read-all jobs: analyze: - uses: apache/logging-parent/.github/workflows/codeql-analysis-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/codeql-analysis-reusable.yaml@rel/12.0.0 with: java-version: | 8 diff --git a/.github/workflows/deploy-site.yaml b/.github/workflows/deploy-site.yaml index 365b8fc2901..604ac5f63aa 100644 --- a/.github/workflows/deploy-site.yaml +++ b/.github/workflows/deploy-site.yaml @@ -33,7 +33,7 @@ jobs: deploy-site-stg: if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x' - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/12.0.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} @@ -51,7 +51,7 @@ jobs: deploy-site-pro: if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x-site-pro' - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/12.0.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} @@ -81,7 +81,7 @@ jobs: deploy-site-rel: needs: export-version - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/12.0.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} diff --git a/.github/workflows/merge-dependabot.yaml b/.github/workflows/merge-dependabot.yaml index c81076e1860..cfb0b1d63dd 100644 --- a/.github/workflows/merge-dependabot.yaml +++ b/.github/workflows/merge-dependabot.yaml @@ -30,7 +30,7 @@ jobs: build: if: github.repository == 'apache/logging-log4j2' && github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]' - uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/12.0.0 secrets: DV_ACCESS_TOKEN: ${{ secrets.DEVELOCITY_ACCESS_KEY }} with: @@ -42,7 +42,7 @@ jobs: merge-dependabot: needs: build - uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@rel/11.3.0 + uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@rel/12.0.0 with: java-version: 17 permissions: diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index c429feaf95a..d72e426442c 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -66,6 +66,7 @@ 3.27.3 4.2.2 2.0b6 + 7.1.0 3.11.18 3.11.5 1.17.1 @@ -125,8 +126,11 @@ 2.0.8 6.0.0 + 2.0.0 + 1.1.2 4.14.0 3.6.0 + 4.9.1 2.7.18 5.3.39 2.0.3 @@ -574,6 +578,12 @@ ${jna.version} + + org.jspecify + jspecify + ${jspecify.version} + + junit junit @@ -829,6 +839,7 @@ biz.aQute.bnd biz.aQute.bnd.annotation + ${bnd.annotation.version} provided @@ -841,19 +852,22 @@ org.osgi - osgi.annotation + org.osgi.annotation.bundle + ${osgi.annotation.bundle.version} provided org.osgi - org.osgi.annotation.bundle + org.osgi.annotation.versioning + ${osgi.annotation.versioning.version} provided com.github.spotbugs spotbugs-annotations + ${spotbugs-annotations.version} provided @@ -977,6 +991,16 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --should-stop=ifError=FLOW + + + + diff --git a/pom.xml b/pom.xml index 41283036545..177b0531d2c 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.apache.logging logging-parent - 11.3.0 + 12.0.0 @@ -357,6 +357,7 @@ + 1.0.0 0.9.0 21.7.1 10.5.0 @@ -993,6 +994,7 @@ org.jspecify jspecify ${jspecify.version} + test diff --git a/src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml b/src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml index 5b17082960b..0ea7b563939 100644 --- a/src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml +++ b/src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml @@ -3,5 +3,5 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - Update `org.apache.logging:logging-parent` to version `11.3.0` + Update `org.apache.logging:logging-parent` to version `12.0.0` From 382ea9b6194d85b9cd39efebfafec40735200335 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 17 Oct 2024 12:11:24 +0200 Subject: [PATCH 08/42] Run reproducibility check after each deployment This PR starts a separate `verify-reproducibility` job, whenever a snapshot or release is deployed. --- .github/workflows/build.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e4046edb7c3..721a7f8a375 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,7 +38,7 @@ jobs: 8 17 site-enabled: true - reproducibility-check-enabled: ${{ startsWith(github.ref_name, 'release/') }} + reproducibility-check-enabled: false develocity-enabled: ${{ ! startsWith(github.ref_name, 'release/') }} deploy-snapshot: @@ -73,3 +73,14 @@ jobs: 8 17 project-id: log4j + + verify-reproducibility: + needs: [ deploy-snapshot, deploy-release ] + if: ${{ always() && (needs.deploy-snapshot.result == 'success' || needs.deploy-release.result == 'success') }} + uses: apache/logging-parent/.github/workflows/verify-reproducibility-reusable.yaml@rel/12.0.0 + with: + nexus-url: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.nexus-url || needs.deploy-snapshot.outputs.nexus-url }} + # Checkout the repository by branch name, since the deployment job might add commits to the branch + ref: ${{ github.ref_name }} + # Encode the `runs-on` input as JSON array + runs-on: '["ubuntu-latest", "macos-latest", "windows-latest"]' From 38466320b5600967e659243dc213e229903fdff5 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 18 Feb 2025 22:38:36 +0100 Subject: [PATCH 09/42] Run integration tests after each deployment (#3105) This PR starts an `integration-test` job, whenever a snapshot or release is deployed. --- .github/workflows/build.yaml | 15 +++++++++++++-- pom.xml | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 721a7f8a375..ddc88be30d9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -77,10 +77,21 @@ jobs: verify-reproducibility: needs: [ deploy-snapshot, deploy-release ] if: ${{ always() && (needs.deploy-snapshot.result == 'success' || needs.deploy-release.result == 'success') }} + name: "verify-reproducibility (${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.project-version || needs.deploy-snapshot.outputs.project-version }})" uses: apache/logging-parent/.github/workflows/verify-reproducibility-reusable.yaml@rel/12.0.0 with: nexus-url: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.nexus-url || needs.deploy-snapshot.outputs.nexus-url }} - # Checkout the repository by branch name, since the deployment job might add commits to the branch - ref: ${{ github.ref_name }} # Encode the `runs-on` input as JSON array runs-on: '["ubuntu-latest", "macos-latest", "windows-latest"]' + + # Run integration-tests automatically after a snapshot or RC is published + integration-test: + needs: [ deploy-snapshot, deploy-release ] + if: ${{ always() && (needs.deploy-snapshot.result == 'success' || needs.deploy-release.result == 'success') }} + name: "integration-test (${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.project-version || needs.deploy-snapshot.outputs.project-version }})" + uses: apache/logging-log4j-samples/.github/workflows/integration-test.yaml@main + with: + log4j-version: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.project-version || needs.deploy-snapshot.outputs.project-version }} + log4j-repository-url: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.nexus-url || needs.deploy-snapshot.outputs.nexus-url }} + # Use the `main` branch of `logging-log4j-samples` + samples-ref: 'refs/heads/main' diff --git a/pom.xml b/pom.xml index 177b0531d2c..31f27d334be 100644 --- a/pom.xml +++ b/pom.xml @@ -307,7 +307,7 @@ - 2.25.0-SNAPSHOT + 2.25.0.pr3105-SNAPSHOT 2.24.3 2.24.3 From 2b9a15f6eb9dda568f4c25977da71b23c72ada6c Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 18 Feb 2025 23:05:34 +0100 Subject: [PATCH 10/42] Fix revision to `2.25.0-SNAPSHOT` --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 31f27d334be..177b0531d2c 100644 --- a/pom.xml +++ b/pom.xml @@ -307,7 +307,7 @@ - 2.25.0.pr3105-SNAPSHOT + 2.25.0-SNAPSHOT 2.24.3 2.24.3 From f203d86c1a0f06ec7b049d8e67727b05bd4d40a8 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 18 Feb 2025 23:55:11 +0100 Subject: [PATCH 11/42] Fix Nexus URL for snapshots --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ddc88be30d9..3e5377d03c1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -80,7 +80,7 @@ jobs: name: "verify-reproducibility (${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.project-version || needs.deploy-snapshot.outputs.project-version }})" uses: apache/logging-parent/.github/workflows/verify-reproducibility-reusable.yaml@rel/12.0.0 with: - nexus-url: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.nexus-url || needs.deploy-snapshot.outputs.nexus-url }} + nexus-url: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.nexus-url || 'https://repository.apache.org/content/groups/snapshots' }} # Encode the `runs-on` input as JSON array runs-on: '["ubuntu-latest", "macos-latest", "windows-latest"]' From bd4607ce21604dea11326a59a3d24985f4a3b202 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Tue, 18 Feb 2025 23:11:28 +0000 Subject: [PATCH 12/42] Update `org.apache.cassandra:cassandra-all` to version `3.11.19` (#3440) --- log4j-parent/pom.xml | 2 +- .../.2.x.x/update_org_apache_cassandra_cassandra_all.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index d72e426442c..1c6846a594e 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -67,7 +67,7 @@ 4.2.2 2.0b6 7.1.0 - 3.11.18 + 3.11.19 3.11.5 1.17.1 1.27.1 diff --git a/src/changelog/.2.x.x/update_org_apache_cassandra_cassandra_all.xml b/src/changelog/.2.x.x/update_org_apache_cassandra_cassandra_all.xml index 01c4737997c..40a9dcfbec6 100644 --- a/src/changelog/.2.x.x/update_org_apache_cassandra_cassandra_all.xml +++ b/src/changelog/.2.x.x/update_org_apache_cassandra_cassandra_all.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - - Update `org.apache.cassandra:cassandra-all` to version `3.11.18` + + Update `org.apache.cassandra:cassandra-all` to version `3.11.19` From 92d6efb089740b8302ce025650d49e12656b8ec4 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 19 Feb 2025 07:49:29 +0100 Subject: [PATCH 13/42] Activate `bom` profile in `log4j-bom` Adds a `.logging-parent-bom-activator` file to activate the `bom` profile. --- .logging-parent-bom-activator | 16 ++++++++++++++++ pom.xml | 17 ----------------- 2 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 .logging-parent-bom-activator diff --git a/.logging-parent-bom-activator b/.logging-parent-bom-activator new file mode 100644 index 00000000000..e7980a6d13b --- /dev/null +++ b/.logging-parent-bom-activator @@ -0,0 +1,16 @@ +# 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. +## +This file activates the `flatten-bom` profile. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 177b0531d2c..bb67727f5fe 100644 --- a/pom.xml +++ b/pom.xml @@ -563,23 +563,6 @@ - - - org.codehaus.mojo - flatten-maven-plugin - ${flatten-maven-plugin.version} - - - flatten-bom - - flatten - - process-resources - false - - - - From 2202b5847213552b81625d020c527e631eb684fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 19 Feb 2025 11:19:42 +0100 Subject: [PATCH 14/42] Add Nexus URL argument to `generate-email.sh` per `logging-parent` upgrade --- .github/generate-email.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/generate-email.sh b/.github/generate-email.sh index e825293af6d..469023ad819 100755 --- a/.github/generate-email.sh +++ b/.github/generate-email.sh @@ -28,12 +28,12 @@ stderr() { fail_for_invalid_args() { stderr "Invalid arguments!" - stderr "Expected arguments: " + stderr "Expected arguments: " exit 1 } # Check arguments -[ $# -ne 3 ] && fail_for_invalid_args +[ $# -ne 4 ] && fail_for_invalid_args # Constants PROJECT_NAME="Apache Log4j" @@ -43,6 +43,7 @@ PROJECT_SITE="https://logging.apache.org/$PROJECT_ID" PROJECT_STAGING_SITE="${PROJECT_SITE/apache.org/staged.apache.org}" PROJECT_REPO="https://github.com/apache/logging-log4j2" COMMIT_ID="$3" +NEXUS_URL="$4" PROJECT_DIST_URL="https://dist.apache.org/repos/dist/dev/logging/$PROJECT_ID/$PROJECT_VERSION" # Check release notes file @@ -71,7 +72,7 @@ Website: $PROJECT_STAGING_SITE/$PROJECT_VERSION/index.html GitHub: $PROJECT_REPO Commit: $COMMIT_ID Distribution: $PROJECT_DIST_URL -Nexus: https://repository.apache.org/content/repositories/orgapachelogging- +Nexus: $NEXUS_URL Signing key: 0x077e8893a6dcc33dd4a4d5b256e73ba9a0b592d0 Review kit: https://logging.apache.org/logging-parent/release-review-instructions.html From c3fa9462cd91fcfd6a66747213432df68ff18933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 19 Feb 2025 11:23:57 +0100 Subject: [PATCH 15/42] Document `maven-compiler-plugin` override --- log4j-parent/pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 1c6846a594e..7b7ddeb8766 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -991,6 +991,9 @@ + org.apache.maven.plugins maven-compiler-plugin From ae77c09f923de98bdfc3f92e15c7c55baa34fab6 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 19 Feb 2025 11:47:02 +0000 Subject: [PATCH 16/42] Update `org.mongodb:bson` to version `5.3.1` (#3409) --- log4j-mongodb/pom.xml | 2 +- src/changelog/.2.x.x/update_org_mongodb_bson.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-mongodb/pom.xml b/log4j-mongodb/pom.xml index 4991ab5ac83..822f4d382db 100644 --- a/log4j-mongodb/pom.xml +++ b/log4j-mongodb/pom.xml @@ -30,7 +30,7 @@ org.apache.logging.log4j.core - 5.2.1 + 5.3.1 2.0.16 diff --git a/src/changelog/.2.x.x/update_org_mongodb_bson.xml b/src/changelog/.2.x.x/update_org_mongodb_bson.xml index 02f463185e0..ce86df17157 100644 --- a/src/changelog/.2.x.x/update_org_mongodb_bson.xml +++ b/src/changelog/.2.x.x/update_org_mongodb_bson.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - - Update `org.mongodb:bson` to version `4.11.5` + + Update `org.mongodb:bson` to version `5.3.1` From 0891d6ba5f01bca8472937fd47ff87f74169b8ac Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 19 Feb 2025 16:35:26 +0100 Subject: [PATCH 17/42] Fix formatting of `s` pattern (#3469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the formatting of the single `s` pattern. Co-authored-by: Volkan Yazıcı --- .../InstantPatternDynamicFormatterTest.java | 29 ++++---- .../InstantPatternDynamicFormatter.java | 66 +++++++++++-------- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java index 7829d234aaa..307511f5bd4 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java @@ -65,13 +65,13 @@ static List sequencingTestCases() { testCases.add(Arguments.of( "yyyyMMddHHmmssSSSX", ChronoUnit.HOURS, - asList(pDyn("yyyyMMddHH", ChronoUnit.HOURS), pDyn("mm"), pSec("", 3), pDyn("X")))); + asList(pDyn("yyyyMMddHH", ChronoUnit.HOURS), pDyn("mm"), pSec(2, "", 3), pDyn("X")))); // `yyyyMMddHHmmssSSSX` instant cache updated per minute testCases.add(Arguments.of( "yyyyMMddHHmmssSSSX", ChronoUnit.MINUTES, - asList(pDyn("yyyyMMddHHmm", ChronoUnit.MINUTES), pSec("", 3), pDyn("X")))); + asList(pDyn("yyyyMMddHHmm", ChronoUnit.MINUTES), pSec(2, "", 3), pDyn("X")))); // ISO9601 instant cache updated daily final String iso8601InstantPattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX"; @@ -81,24 +81,29 @@ static List sequencingTestCases() { asList( pDyn("yyyy'-'MM'-'dd'T'", ChronoUnit.DAYS), pDyn("HH':'mm':'", ChronoUnit.MINUTES), - pSec(".", 3), + pSec(2, ".", 3), pDyn("X")))); // ISO9601 instant cache updated per minute testCases.add(Arguments.of( iso8601InstantPattern, ChronoUnit.MINUTES, - asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(".", 3), pDyn("X")))); + asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(2, ".", 3), pDyn("X")))); // ISO9601 instant cache updated per second testCases.add(Arguments.of( iso8601InstantPattern, ChronoUnit.SECONDS, - asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(".", 3), pDyn("X")))); + asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(2, ".", 3), pDyn("X")))); // Seconds and micros testCases.add(Arguments.of( - "HH:mm:ss.SSSSSS", ChronoUnit.MINUTES, asList(pDyn("HH':'mm':'", ChronoUnit.MINUTES), pSec(".", 6)))); + "HH:mm:ss.SSSSSS", + ChronoUnit.MINUTES, + asList(pDyn("HH':'mm':'", ChronoUnit.MINUTES), pSec(2, ".", 6)))); + + // Seconds without padding + testCases.add(Arguments.of("s.SSS", ChronoUnit.SECONDS, singletonList(pSec(1, ".", 3)))); return testCases; } @@ -111,12 +116,12 @@ private static DynamicPatternSequence pDyn(final String pattern, final ChronoUni return new DynamicPatternSequence(pattern, precision); } - private static SecondPatternSequence pSec(String separator, int fractionalDigits) { - return new SecondPatternSequence(true, separator, fractionalDigits); + private static SecondPatternSequence pSec(int secondDigits, String separator, int fractionalDigits) { + return new SecondPatternSequence(secondDigits, separator, fractionalDigits); } private static SecondPatternSequence pMilliSec() { - return new SecondPatternSequence(false, "", 3); + return new SecondPatternSequence(0, "", 3); } @ParameterizedTest @@ -352,7 +357,9 @@ static Stream formatterInputs() { "yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss.SSSSSS", "dd/MM/yy HH:mm:ss.SSS", - "dd/MM/yyyy HH:mm:ss.SSS") + "dd/MM/yyyy HH:mm:ss.SSS", + // seconds without padding + "s.SSS") .flatMap(InstantPatternDynamicFormatterTest::formatterInputs); } @@ -364,7 +371,7 @@ static Stream formatterInputs() { Arrays.stream(TimeZone.getAvailableIDs()).map(TimeZone::getTimeZone).toArray(TimeZone[]::new); static Stream formatterInputs(final String pattern) { - return IntStream.range(0, 500).mapToObj(ignoredIndex -> { + return IntStream.range(0, 100).mapToObj(ignoredIndex -> { final Locale locale = LOCALES[RANDOM.nextInt(LOCALES.length)]; final TimeZone timeZone = TIME_ZONES[RANDOM.nextInt(TIME_ZONES.length)]; final MutableInstant instant = randomInstant(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatter.java index bb8059329ea..1c9dfab5711 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatter.java @@ -239,10 +239,10 @@ private static List sequencePattern(final String pattern) { final PatternSequence sequence; switch (c) { case 's': - sequence = new SecondPatternSequence(true, "", 0); + sequence = new SecondPatternSequence(sequenceContent.length(), "", 0); break; case 'S': - sequence = new SecondPatternSequence(false, "", sequenceContent.length()); + sequence = new SecondPatternSequence(0, "", sequenceContent.length()); break; default: sequence = new DynamicPatternSequence(sequenceContent); @@ -694,39 +694,50 @@ static class SecondPatternSequence extends PatternSequence { 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1 }; - private final boolean printSeconds; + private final int secondDigits; private final String separator; private final int fractionalDigits; - SecondPatternSequence(boolean printSeconds, String separator, int fractionalDigits) { + SecondPatternSequence(int secondDigits, String separator, int fractionalDigits) { super( - createPattern(printSeconds, separator, fractionalDigits), - determinePrecision(printSeconds, fractionalDigits)); - this.printSeconds = printSeconds; + createPattern(secondDigits, separator, fractionalDigits), + determinePrecision(secondDigits, fractionalDigits)); + final int maxSecondDigits = 2; + if (secondDigits > maxSecondDigits) { + final String message = String.format( + "More than %d `s` pattern letters are not supported, found: %d", maxSecondDigits, secondDigits); + throw new IllegalArgumentException(message); + } + final int maxFractionalDigits = 9; + if (fractionalDigits > maxFractionalDigits) { + final String message = String.format( + "More than %d `S` pattern letters are not supported, found: %d", + maxFractionalDigits, fractionalDigits); + throw new IllegalArgumentException(message); + } + this.secondDigits = secondDigits; this.separator = separator; this.fractionalDigits = fractionalDigits; } - private static String createPattern(boolean printSeconds, String separator, int fractionalDigits) { - StringBuilder builder = new StringBuilder(); - if (printSeconds) { - builder.append("ss"); - } - builder.append(StaticPatternSequence.escapeLiteral(separator)); - if (fractionalDigits > 0) { - builder.append(Strings.repeat("S", fractionalDigits)); - } - return builder.toString(); + private static String createPattern(int secondDigits, String separator, int fractionalDigits) { + return Strings.repeat("s", secondDigits) + + StaticPatternSequence.escapeLiteral(separator) + + Strings.repeat("S", fractionalDigits); } - private static ChronoUnit determinePrecision(boolean printSeconds, int digits) { + private static ChronoUnit determinePrecision(int secondDigits, int digits) { if (digits > 6) return ChronoUnit.NANOS; if (digits > 3) return ChronoUnit.MICROS; if (digits > 0) return ChronoUnit.MILLIS; - return printSeconds ? ChronoUnit.SECONDS : ChronoUnit.FOREVER; + return secondDigits > 0 ? ChronoUnit.SECONDS : ChronoUnit.FOREVER; + } + + private static void formatUnpaddedSeconds(StringBuilder buffer, Instant instant) { + buffer.append(instant.getEpochSecond() % 60L); } - private static void formatSeconds(StringBuilder buffer, Instant instant) { + private static void formatPaddedSeconds(StringBuilder buffer, Instant instant) { long secondsInMinute = instant.getEpochSecond() % 60L; buffer.append((char) ((secondsInMinute / 10L) + '0')); buffer.append((char) ((secondsInMinute % 10L) + '0')); @@ -757,9 +768,12 @@ private static void formatMillis(StringBuilder buffer, Instant instant) { @Override InstantPatternFormatter createFormatter(Locale locale, TimeZone timeZone) { + final BiConsumer secondDigitsFormatter = secondDigits == 2 + ? SecondPatternSequence::formatPaddedSeconds + : SecondPatternSequence::formatUnpaddedSeconds; final BiConsumer fractionDigitsFormatter = fractionalDigits == 3 ? SecondPatternSequence::formatMillis : this::formatFractionalDigits; - if (!printSeconds) { + if (secondDigits == 0) { return new AbstractFormatter(pattern, locale, timeZone, precision) { @Override public void formatTo(StringBuilder buffer, Instant instant) { @@ -772,7 +786,7 @@ public void formatTo(StringBuilder buffer, Instant instant) { return new AbstractFormatter(pattern, locale, timeZone, precision) { @Override public void formatTo(StringBuilder buffer, Instant instant) { - formatSeconds(buffer, instant); + secondDigitsFormatter.accept(buffer, instant); buffer.append(separator); } }; @@ -780,7 +794,7 @@ public void formatTo(StringBuilder buffer, Instant instant) { return new AbstractFormatter(pattern, locale, timeZone, precision) { @Override public void formatTo(StringBuilder buffer, Instant instant) { - formatSeconds(buffer, instant); + secondDigitsFormatter.accept(buffer, instant); buffer.append(separator); fractionDigitsFormatter.accept(buffer, instant); } @@ -795,15 +809,15 @@ PatternSequence tryMerge(PatternSequence other, ChronoUnit thresholdPrecision) { StaticPatternSequence staticOther = (StaticPatternSequence) other; if (fractionalDigits == 0) { return new SecondPatternSequence( - printSeconds, this.separator + staticOther.literal, fractionalDigits); + this.secondDigits, this.separator + staticOther.literal, fractionalDigits); } } // We can always append more fractional digits if (other instanceof SecondPatternSequence) { SecondPatternSequence secondOther = (SecondPatternSequence) other; - if (!secondOther.printSeconds && secondOther.separator.isEmpty()) { + if (secondOther.secondDigits == 0 && secondOther.separator.isEmpty()) { return new SecondPatternSequence( - printSeconds, this.separator, this.fractionalDigits + secondOther.fractionalDigits); + this.secondDigits, this.separator, this.fractionalDigits + secondOther.fractionalDigits); } } return null; From eefffd90f61950e5e75d9278879ea7454fbb150e Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 19 Feb 2025 16:03:13 +0000 Subject: [PATCH 18/42] Update `co.elastic.clients:elasticsearch-java` to version `8.17.2` (#3460) --- log4j-layout-template-json-test/pom.xml | 2 +- .../.2.x.x/update_co_elastic_clients_elasticsearch_java.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-layout-template-json-test/pom.xml b/log4j-layout-template-json-test/pom.xml index 0116cd7561c..6182764d1c9 100644 --- a/log4j-layout-template-json-test/pom.xml +++ b/log4j-layout-template-json-test/pom.xml @@ -48,7 +48,7 @@ 2. The Docker image version of the ELK-stack As of 2024-09-16, these all (Maven artifacts and Elastic products) get released with the same version. --> - 8.17.0 + 8.17.2 diff --git a/src/changelog/.2.x.x/update_co_elastic_clients_elasticsearch_java.xml b/src/changelog/.2.x.x/update_co_elastic_clients_elasticsearch_java.xml index 85ee84ee0c4..d2312d6073d 100644 --- a/src/changelog/.2.x.x/update_co_elastic_clients_elasticsearch_java.xml +++ b/src/changelog/.2.x.x/update_co_elastic_clients_elasticsearch_java.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - - Update `co.elastic.clients:elasticsearch-java` to version `8.17.0` + + Update `co.elastic.clients:elasticsearch-java` to version `8.17.2` From 71a03d754214e3a3b858b3e65bc96c953b76dd1f Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 19 Feb 2025 20:01:35 +0000 Subject: [PATCH 19/42] Update `commons-codec:commons-codec` to version `1.18.0` (#3421) --- log4j-parent/pom.xml | 2 +- .../.2.x.x/update_commons_codec_commons_codec.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/update_commons_codec_commons_codec.xml diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 7b7ddeb8766..771e42dc118 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -69,7 +69,7 @@ 7.1.0 3.11.19 3.11.5 - 1.17.1 + 1.18.0 1.27.1 1.13.0 2.13.0 diff --git a/src/changelog/.2.x.x/update_commons_codec_commons_codec.xml b/src/changelog/.2.x.x/update_commons_codec_commons_codec.xml new file mode 100644 index 00000000000..dd89663c0b9 --- /dev/null +++ b/src/changelog/.2.x.x/update_commons_codec_commons_codec.xml @@ -0,0 +1,8 @@ + + + + Update `commons-codec:commons-codec` to version `1.18.0` + From f98bff4d662f6c4d9893af197188e1391ec5e74d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:56:54 +0100 Subject: [PATCH 20/42] Bump commons-logging:commons-logging in /log4j-parent (#3445) Bumps commons-logging:commons-logging from 1.3.4 to 1.3.5. --- updated-dependencies: - dependency-name: commons-logging:commons-logging dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- log4j-parent/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 771e42dc118..8a8d9de2397 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -75,7 +75,7 @@ 2.13.0 2.18.0 3.17.0 - 1.3.4 + 1.3.5 1.2.15 3.4.4 diff --git a/pom.xml b/pom.xml index bb67727f5fe..381e9aec3c1 100644 --- a/pom.xml +++ b/pom.xml @@ -341,7 +341,7 @@ true 1.27.1 1.13.0 - 1.3.4 + 1.3.5 1.2.21 4.0.0 1.11.0 From 5aac7d6ce9f077fd0006cd8975eac5baf32187a6 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 19 Feb 2025 23:25:17 +0000 Subject: [PATCH 21/42] Update `org.openrewrite.recipe:rewrite-logging-frameworks` to version `3.2.0` (#3446) --- pom.xml | 2 +- ...date_org_openrewrite_recipe_rewrite_logging_frameworks.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 381e9aec3c1..1fb7338f7ca 100644 --- a/pom.xml +++ b/pom.xml @@ -1059,7 +1059,7 @@ org.openrewrite.recipe rewrite-logging-frameworks - 2.3.0 + 3.2.0 diff --git a/src/changelog/.2.x.x/update_org_openrewrite_recipe_rewrite_logging_frameworks.xml b/src/changelog/.2.x.x/update_org_openrewrite_recipe_rewrite_logging_frameworks.xml index 7107b37660a..910698cf1e6 100644 --- a/src/changelog/.2.x.x/update_org_openrewrite_recipe_rewrite_logging_frameworks.xml +++ b/src/changelog/.2.x.x/update_org_openrewrite_recipe_rewrite_logging_frameworks.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - - Update `org.openrewrite.recipe:rewrite-logging-frameworks` to version `2.3.0` + + Update `org.openrewrite.recipe:rewrite-logging-frameworks` to version `3.2.0` From 07590bc087204ccd4d091c65d0c5da255c92df68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 20 Feb 2025 18:07:55 +0100 Subject: [PATCH 22/42] Add `collectionName` and `databaseName` attributes to `MongoDbProvider` (#3467) Co-authored-by: Josh Smith --- .../mongodb/MongoDbCollectionNameIT.java | 36 ++++++ .../MongoDbDatabaseAndCollectionNameIT.java | 36 ++++++ .../MongoDbNoDatabaseAndCollectionNameIT.java | 36 ++++++ .../resources/MongoDbCollectionNameIT.xml | 37 ++++++ .../MongoDbDatabaseAndCollectionNameIT.xml | 39 ++++++ .../MongoDbNoDatabaseAndCollectionNameIT.xml | 37 ++++++ log4j-mongodb4/pom.xml | 15 --- .../log4j/mongodb4/MongoDb4Connection.java | 30 ++++- .../log4j/mongodb4/MongoDb4Provider.java | 110 +++++++++++++---- .../mongodb4/AbstractMongoDb4CappedIT.java | 6 +- .../mongodb4/MongoDb4CollectionNameIT.java | 36 ++++++ .../MongoDb4DatabaseAndCollectionNameIT.java | 36 ++++++ .../log4j/mongodb4/MongoDb4ProviderTest.java | 116 ++++++++++++++++++ .../log4j/mongodb4/MongoDb4Resolver.java | 2 - .../resources/MongoDb4CollectionNameIT.xml | 36 ++++++ .../MongoDb4DatabaseAndCollectionNameIT.xml | 38 ++++++ .../.2.x.x/3467_add_mongodb_conn_db_name.xml | 8 ++ .../appenders/database/nosql-mongo-keys.json | 4 +- .../database/nosql-mongo-keys.properties | 4 +- .../appenders/database/nosql-mongo-keys.xml | 4 +- .../appenders/database/nosql-mongo-keys.yaml | 4 +- .../appenders/database/nosql-mongo.json | 4 +- .../appenders/database/nosql-mongo.properties | 4 +- .../manual/appenders/database/nosql-mongo.xml | 4 +- .../appenders/database/nosql-mongo.yaml | 4 +- .../ROOT/pages/manual/appenders/database.adoc | 32 +++-- 26 files changed, 655 insertions(+), 63 deletions(-) create mode 100644 log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCollectionNameIT.java create mode 100644 log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbDatabaseAndCollectionNameIT.java create mode 100644 log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbNoDatabaseAndCollectionNameIT.java create mode 100644 log4j-mongodb/src/test/resources/MongoDbCollectionNameIT.xml create mode 100644 log4j-mongodb/src/test/resources/MongoDbDatabaseAndCollectionNameIT.xml create mode 100644 log4j-mongodb/src/test/resources/MongoDbNoDatabaseAndCollectionNameIT.xml create mode 100644 log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CollectionNameIT.java create mode 100644 log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4DatabaseAndCollectionNameIT.java create mode 100644 log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ProviderTest.java create mode 100644 log4j-mongodb4/src/test/resources/MongoDb4CollectionNameIT.xml create mode 100644 log4j-mongodb4/src/test/resources/MongoDb4DatabaseAndCollectionNameIT.xml create mode 100644 src/changelog/.2.x.x/3467_add_mongodb_conn_db_name.xml diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCollectionNameIT.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCollectionNameIT.java new file mode 100644 index 00000000000..60c74dff597 --- /dev/null +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCollectionNameIT.java @@ -0,0 +1,36 @@ +/* + * 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.mongodb; + +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingMongoDb +@LoggerContextSource("MongoDbCollectionNameIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbCollectionNameIT extends AbstractMongoDbCappedIT { + + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } +} diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbDatabaseAndCollectionNameIT.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbDatabaseAndCollectionNameIT.java new file mode 100644 index 00000000000..8d399d10ac9 --- /dev/null +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbDatabaseAndCollectionNameIT.java @@ -0,0 +1,36 @@ +/* + * 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.mongodb; + +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingMongoDb +@LoggerContextSource("MongoDbDatabaseAndCollectionNameIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbDatabaseAndCollectionNameIT extends AbstractMongoDbCappedIT { + + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } +} diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbNoDatabaseAndCollectionNameIT.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbNoDatabaseAndCollectionNameIT.java new file mode 100644 index 00000000000..ba6a654fdb9 --- /dev/null +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbNoDatabaseAndCollectionNameIT.java @@ -0,0 +1,36 @@ +/* + * 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.mongodb; + +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingMongoDb +@LoggerContextSource("MongoDbNoDatabaseAndCollectionNameIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbNoDatabaseAndCollectionNameIT extends AbstractMongoDbCappedIT { + + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } +} diff --git a/log4j-mongodb/src/test/resources/MongoDbCollectionNameIT.xml b/log4j-mongodb/src/test/resources/MongoDbCollectionNameIT.xml new file mode 100644 index 00000000000..4b9b53842ed --- /dev/null +++ b/log4j-mongodb/src/test/resources/MongoDbCollectionNameIT.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/log4j-mongodb/src/test/resources/MongoDbDatabaseAndCollectionNameIT.xml b/log4j-mongodb/src/test/resources/MongoDbDatabaseAndCollectionNameIT.xml new file mode 100644 index 00000000000..b23b551fa21 --- /dev/null +++ b/log4j-mongodb/src/test/resources/MongoDbDatabaseAndCollectionNameIT.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + diff --git a/log4j-mongodb/src/test/resources/MongoDbNoDatabaseAndCollectionNameIT.xml b/log4j-mongodb/src/test/resources/MongoDbNoDatabaseAndCollectionNameIT.xml new file mode 100644 index 00000000000..0068ab3ee38 --- /dev/null +++ b/log4j-mongodb/src/test/resources/MongoDbNoDatabaseAndCollectionNameIT.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/log4j-mongodb4/pom.xml b/log4j-mongodb4/pom.xml index fc25d4ace9a..9115fe4175f 100644 --- a/log4j-mongodb4/pom.xml +++ b/log4j-mongodb4/pom.xml @@ -134,16 +134,6 @@ org.apache.maven.plugins maven-surefire-plugin - - true - - - - org.junit.jupiter - junit-jupiter-engine - ${junit-jupiter.version} - - @@ -236,11 +226,6 @@ **/*IT.java - - OFF ${mongo.port} diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java index 1d9537978c4..dd7a3a927c1 100644 --- a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java +++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java @@ -41,7 +41,7 @@ public final class MongoDb4Connection extends AbstractNoSqlConnection getOrCreateMongoCollection( final MongoDatabase database, final String collectionName, final boolean isCapped, final Long sizeInBytes) { try { - LOGGER.debug("Gettting collection '{}'...", collectionName); + LOGGER.debug("Getting collection '{}'...", collectionName); // throws IllegalArgumentException if collectionName is invalid final MongoCollection found = database.getCollection(collectionName); LOGGER.debug("Got collection {}", found); @@ -63,15 +63,29 @@ private static MongoCollection getOrCreateMongoCollection( private final MongoCollection collection; private final MongoClient mongoClient; + /** + * @deprecated Use {@link #MongoDb4Connection(ConnectionString, MongoClient, MongoDatabase, String, boolean, Long)} instead + */ + @Deprecated public MongoDb4Connection( final ConnectionString connectionString, final MongoClient mongoClient, final MongoDatabase mongoDatabase, final boolean isCapped, final Integer sizeInBytes) { - this(connectionString, mongoClient, mongoDatabase, isCapped, Long.valueOf(sizeInBytes)); + this( + connectionString, + mongoClient, + mongoDatabase, + connectionString.getCollection(), + isCapped, + Long.valueOf(sizeInBytes)); } + /** + * @deprecated Use {@link #MongoDb4Connection(ConnectionString, MongoClient, MongoDatabase, String, boolean, Long)} instead + */ + @Deprecated public MongoDb4Connection( final ConnectionString connectionString, final MongoClient mongoClient, @@ -84,6 +98,18 @@ public MongoDb4Connection( getOrCreateMongoCollection(mongoDatabase, connectionString.getCollection(), isCapped, sizeInBytes); } + public MongoDb4Connection( + final ConnectionString connectionString, + final MongoClient mongoClient, + final MongoDatabase mongoDatabase, + final String collectionName, + final boolean isCapped, + final Long sizeInBytes) { + this.connectionString = connectionString; + this.mongoClient = mongoClient; + this.collection = getOrCreateMongoCollection(mongoDatabase, collectionName, isCapped, sizeInBytes); + } + @Override public void closeImpl() { // LOG4J2-1196 diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java index 99e392237b4..18cf93f2356 100644 --- a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java +++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java @@ -18,10 +18,10 @@ import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoNamespace; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoDatabase; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider; import org.apache.logging.log4j.core.config.plugins.Plugin; @@ -40,6 +40,8 @@ @Plugin(name = MongoDb4Provider.PLUGIN_NAME, category = Core.CATEGORY_NAME, printObject = true) public final class MongoDb4Provider implements NoSqlProvider { + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + static final String PLUGIN_NAME = "MongoDb4"; /** @@ -60,14 +62,20 @@ public static class Builder> extends AbstractFilterable.Bui @PluginBuilderAttribute("capped") private boolean capped = false; + @PluginBuilderAttribute("collectionName") + private String collectionName; + + @PluginBuilderAttribute("databaseName") + private String databaseName; + @Override public MongoDb4Provider build() { - StatusLogger.getLogger().warn("The {} Appender is deprecated, use the MongoDb Appender.", PLUGIN_NAME); + LOGGER.warn("The {} Appender is deprecated, use the MongoDb Appender instead.", PLUGIN_NAME); return newMongoDb4Provider(); } protected MongoDb4Provider newMongoDb4Provider() { - return new MongoDb4Provider(connectionStringSource, capped, collectionSize); + return new MongoDb4Provider(connectionStringSource, databaseName, collectionName, capped, collectionSize); } /** @@ -113,16 +121,34 @@ public B setCollectionSize(final long sizeInBytes) { this.collectionSize = sizeInBytes; return asBuilder(); } - } - private static final Logger LOGGER = StatusLogger.getLogger(); + /** + * Sets name of the collection for the appender to output to + * + * @param collectionName the name of the collection for the appender to output to + * @return this instance. + */ + public B setCollectionName(final String collectionName) { + this.collectionName = collectionName; + return asBuilder(); + } + + /** + * Sets the name of the logical database for the appender to output to. + * + * @param databaseName the name of the DB for the appender to output to + * @return this instance. + */ + public B setDatabaseName(final String databaseName) { + this.databaseName = databaseName; + return asBuilder(); + } + } - // @formatter:off private static final CodecRegistry CODEC_REGISTRIES = CodecRegistries.fromRegistries( MongoClientSettings.getDefaultCodecRegistry(), CodecRegistries.fromCodecs(MongoDb4LevelCodec.INSTANCE), CodecRegistries.fromCodecs(new MongoDb4DocumentObjectCodec())); - // @formatter:on // TODO Where does this number come from? private static final long DEFAULT_COLLECTION_SIZE = 536_870_912; @@ -140,47 +166,81 @@ public static > B newBuilder() { private final Long collectionSize; private final boolean isCapped; + private final String collectionName; private final MongoClient mongoClient; private final MongoDatabase mongoDatabase; private final ConnectionString connectionString; - private MongoDb4Provider(final String connectionStringSource, final boolean isCapped, final Long collectionSize) { - LOGGER.debug("Creating ConnectionString {}...", connectionStringSource); - this.connectionString = new ConnectionString(connectionStringSource); - LOGGER.debug("Created ConnectionString {}", connectionString); - LOGGER.debug("Creating MongoClientSettings..."); - // @formatter:off + private MongoDb4Provider( + final String connectionStringSource, + final String databaseName, + final String collectionName, + final boolean isCapped, + final Long collectionSize) { + this.connectionString = createConnectionString(connectionStringSource); final MongoClientSettings settings = MongoClientSettings.builder() .applyConnectionString(this.connectionString) .codecRegistry(CODEC_REGISTRIES) .build(); - // @formatter:on - LOGGER.debug("Created MongoClientSettings {}", settings); - LOGGER.debug("Creating MongoClient {}...", settings); this.mongoClient = MongoClients.create(settings); - LOGGER.debug("Created MongoClient {}", mongoClient); - final String databaseName = this.connectionString.getDatabase(); - LOGGER.debug("Getting MongoDatabase {}...", databaseName); - this.mongoDatabase = this.mongoClient.getDatabase(databaseName); - LOGGER.debug("Got MongoDatabase {}", mongoDatabase); + this.mongoDatabase = createDatabase(connectionString, databaseName, mongoClient); this.isCapped = isCapped; this.collectionSize = collectionSize; + this.collectionName = getEffectiveCollectionName(connectionString, collectionName); + LOGGER.debug("instantiated {}", this); + } + + private static ConnectionString createConnectionString(final String connectionStringSource) { + try { + return new ConnectionString(connectionStringSource); + } catch (final IllegalArgumentException error) { + final String message = String.format("Invalid MongoDB connection string: `%s`", connectionStringSource); + throw new IllegalArgumentException(message, error); + } + } + + private static MongoDatabase createDatabase( + final ConnectionString connectionString, final String databaseName, final MongoClient client) { + final String effectiveDatabaseName = databaseName != null ? databaseName : connectionString.getDatabase(); + try { + // noinspection DataFlowIssue + MongoNamespace.checkDatabaseNameValidity(effectiveDatabaseName); + } catch (final IllegalArgumentException error) { + final String message = String.format("Invalid MongoDB database name: `%s`", effectiveDatabaseName); + throw new IllegalArgumentException(message, error); + } + return client.getDatabase(effectiveDatabaseName); + } + + private static String getEffectiveCollectionName( + final ConnectionString connectionString, final String collectionName) { + final String effectiveCollectionName = + collectionName != null ? collectionName : connectionString.getCollection(); + try { + // noinspection DataFlowIssue + MongoNamespace.checkCollectionNameValidity(effectiveCollectionName); + } catch (final IllegalArgumentException error) { + final String message = String.format("Invalid MongoDB collection name: `%s`", effectiveCollectionName); + throw new IllegalArgumentException(message, error); + } + return effectiveCollectionName; } @Override public MongoDb4Connection getConnection() { - return new MongoDb4Connection(connectionString, mongoClient, mongoDatabase, isCapped, collectionSize); + return new MongoDb4Connection( + connectionString, mongoClient, mongoDatabase, collectionName, isCapped, collectionSize); } @Override public String toString() { return String.format( - "%s [connectionString=%s, collectionSize=%s, isCapped=%s, mongoClient=%s, mongoDatabase=%s]", + "%s [connectionString=`%s`, collectionSize=%s, isCapped=%s, databaseName=`%s`, collectionName=`%s`]", MongoDb4Provider.class.getSimpleName(), connectionString, collectionSize, isCapped, - mongoClient, - mongoDatabase); + mongoDatabase.getName(), + collectionName); } } diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedIT.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedIT.java index 07483dd3cf2..caac94e6b5e 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedIT.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedIT.java @@ -24,6 +24,7 @@ import com.mongodb.client.MongoDatabase; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.status.StatusLogger; import org.bson.Document; abstract class AbstractMongoDb4CappedIT { @@ -33,8 +34,9 @@ protected void test(final LoggerContext ctx, final MongoClient mongoClient) { logger.info("Hello log"); final MongoDatabase database = mongoClient.getDatabase(MongoDb4TestConstants.DATABASE_NAME); assertNotNull(database); - final MongoCollection collection = - database.getCollection(getClass().getSimpleName()); + final String collectionName = getClass().getSimpleName(); + StatusLogger.getLogger().debug("Using collection name: {}", collectionName); + final MongoCollection collection = database.getCollection(collectionName); assertNotNull(collection); final Document first = collection.find().first(); assertNotNull(first); diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CollectionNameIT.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CollectionNameIT.java new file mode 100644 index 00000000000..f714ee2eb82 --- /dev/null +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CollectionNameIT.java @@ -0,0 +1,36 @@ +/* + * 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.mongodb4; + +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingMongoDb4 +@LoggerContextSource("MongoDb4CollectionNameIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4CollectionNameIT extends AbstractMongoDb4CappedIT { + + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } +} diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4DatabaseAndCollectionNameIT.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4DatabaseAndCollectionNameIT.java new file mode 100644 index 00000000000..3c06692a64a --- /dev/null +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4DatabaseAndCollectionNameIT.java @@ -0,0 +1,36 @@ +/* + * 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.mongodb4; + +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingMongoDb4 +@LoggerContextSource("MongoDb4DatabaseAndCollectionNameIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4DatabaseAndCollectionNameIT extends AbstractMongoDb4CappedIT { + + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } +} diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ProviderTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ProviderTest.java new file mode 100644 index 00000000000..8588ead05ec --- /dev/null +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ProviderTest.java @@ -0,0 +1,116 @@ +/* + * 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.mongodb4; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.mongodb.MongoNamespace; +import com.mongodb.client.MongoCollection; +import java.lang.reflect.Field; +import org.bson.Document; +import org.junit.jupiter.api.Test; + +class MongoDb4ProviderTest { + + private static final String CON_STR_WO_DB = "mongodb://localhost:27017"; + + private static final String CON_STR_W_DB = "mongodb://localhost:27017/logging"; + + private static final String CON_STR_DB_COLL = "mongodb://localhost:27017/logging.logs"; + + private static final String COLLECTION_NAME = "logsTest"; + + private static final String DATABASE_NAME = "loggingTest"; + + @Test + void createProviderWithDatabaseAndCollectionProvidedViaConfig() { + MongoDb4Provider provider = MongoDb4Provider.newBuilder() + .setConnectionStringSource(CON_STR_WO_DB) + .setDatabaseName(DATABASE_NAME) + .setCollectionName(COLLECTION_NAME) + .build(); + assertThat(provider).isNotNull(); + assertProviderNamespace(provider, DATABASE_NAME, COLLECTION_NAME); + } + + @Test + void createProviderWithoutDatabaseName() { + assertThatThrownBy(() -> MongoDb4Provider.newBuilder() + .setConnectionStringSource(CON_STR_WO_DB) + .build()) + .hasMessage("Invalid MongoDB database name: `null`"); + } + + @Test + void createProviderWithoutDatabaseNameWithCollectionName() { + assertThatThrownBy(() -> MongoDb4Provider.newBuilder() + .setConnectionStringSource(CON_STR_WO_DB) + .setCollectionName(COLLECTION_NAME) + .build()) + .hasMessage("Invalid MongoDB database name: `null`"); + } + + @Test + void createProviderWithoutCollectionName() { + assertThatThrownBy(() -> MongoDb4Provider.newBuilder() + .setConnectionStringSource(CON_STR_WO_DB) + .setDatabaseName(DATABASE_NAME) + .build()) + .hasMessage("Invalid MongoDB collection name: `null`"); + } + + @Test + void createProviderWithDatabaseOnConnectionString() { + MongoDb4Provider provider = MongoDb4Provider.newBuilder() + .setConnectionStringSource(CON_STR_W_DB) + .setCollectionName(COLLECTION_NAME) + .build(); + assertThat(provider).isNotNull(); + assertProviderNamespace(provider, "logging", COLLECTION_NAME); + } + + @Test + void createProviderConfigOverridesConnectionString() { + MongoDb4Provider provider = MongoDb4Provider.newBuilder() + .setConnectionStringSource(CON_STR_DB_COLL) + .setCollectionName(COLLECTION_NAME) + .setDatabaseName(DATABASE_NAME) + .build(); + assertThat(provider).isNotNull(); + assertProviderNamespace(provider, DATABASE_NAME, COLLECTION_NAME); + } + + private static void assertProviderNamespace(MongoDb4Provider provider, String databaseName, String collectionName) { + MongoNamespace namespace = providerNamespace(provider); + assertThat(namespace.getDatabaseName()).isEqualTo(databaseName); + assertThat(namespace.getCollectionName()).isEqualTo(collectionName); + } + + private static MongoNamespace providerNamespace(MongoDb4Provider provider) { + try { + MongoDb4Connection connection = provider.getConnection(); + Field collectionField = MongoDb4Connection.class.getDeclaredField("collection"); + collectionField.setAccessible(true); + @SuppressWarnings("unchecked") + MongoCollection collection = (MongoCollection) collectionField.get(connection); + return collection.getNamespace(); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java index 0534c09631a..c4e1628db4b 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java @@ -30,8 +30,6 @@ class MongoDb4Resolver extends TypeBasedParameterResolver implements BeforeAllCallback { - static final String PORT_PROPERTY = "log4j2.mongo.port"; - public MongoDb4Resolver() { super(MongoClient.class); } diff --git a/log4j-mongodb4/src/test/resources/MongoDb4CollectionNameIT.xml b/log4j-mongodb4/src/test/resources/MongoDb4CollectionNameIT.xml new file mode 100644 index 00000000000..09834dc526c --- /dev/null +++ b/log4j-mongodb4/src/test/resources/MongoDb4CollectionNameIT.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + diff --git a/log4j-mongodb4/src/test/resources/MongoDb4DatabaseAndCollectionNameIT.xml b/log4j-mongodb4/src/test/resources/MongoDb4DatabaseAndCollectionNameIT.xml new file mode 100644 index 00000000000..790c991ccf7 --- /dev/null +++ b/log4j-mongodb4/src/test/resources/MongoDb4DatabaseAndCollectionNameIT.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + diff --git a/src/changelog/.2.x.x/3467_add_mongodb_conn_db_name.xml b/src/changelog/.2.x.x/3467_add_mongodb_conn_db_name.xml new file mode 100644 index 00000000000..ecf5d7e625b --- /dev/null +++ b/src/changelog/.2.x.x/3467_add_mongodb_conn_db_name.xml @@ -0,0 +1,8 @@ + + + + Add `collectionName` and `databaseName` arguments to the MongoDB appender + diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.json b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.json index 2b47690c0b5..702a4c3591c 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.json +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.json @@ -5,7 +5,9 @@ "NoSql": { "name": "MONGO", "MongoDb": { - "connection": "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/logging.logs" + "connection": "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/", + "databaseName": "logging", + "collectionName": "logs" }, "KeyValuePair": [ { diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.properties b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.properties index e0e97ac2872..ba3112e8951 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.properties +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.properties @@ -19,7 +19,9 @@ appender.0.type = NoSql appender.0.name = MONGO appender.0.provider.type = MongoDB -appender.0.provider.connection = mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/logging.logs +appender.0.provider.connection = mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017 +appender.0.provider.databaseName = logging +appender.0.provider.collectionName = logs appender.0.kv[0].type = KeyValuePair appender.0.kv[0].key = startTime diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.xml b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.xml index cd9f458ac3a..fc4e3b06972 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.xml +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.xml @@ -23,7 +23,9 @@ - + diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.yaml b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.yaml index 2cb5df2c85f..1b286eec629 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.yaml +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo-keys.yaml @@ -20,7 +20,9 @@ Configuration: NoSql: name: "MONGO" MongoDb: - connection: "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/logging.logs" + connection: "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/" + databaseName: "logging" + collectionName: "logs" KeyValuePair: - key: "startTime" value: "${date:yyyy-MM-dd hh:mm:ss.SSS}" # <1> diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.json b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.json index d06b3a190f6..b8bd82389e6 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.json +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.json @@ -10,7 +10,9 @@ "NoSql": { "name": "MONGO", "MongoDb": { - "connection": "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/logging.logs" + "connection": "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/", + "databaseName": "logging", + "collectionName": "logs" } } // end::appender[] diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.properties b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.properties index 6a1e188310c..ab06837d8b9 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.properties +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.properties @@ -22,7 +22,9 @@ appender.0.layout.type = JsonTemplateLayout appender.1.type = NoSql appender.1.name = MONGO appender.1.provider.type = MongoDB -appender.1.provider.connection = mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/logging.logs +appender.1.provider.connection = mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/ +appender.1.provider.databaseName = logging +appender.1.provider.collectionName = logs # end::appender[] # tag::loggers[] rootLogger.level = INFO diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.xml b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.xml index 31fe89f2f19..d5a22a1134b 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.xml +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.xml @@ -27,7 +27,9 @@ - + diff --git a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.yaml b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.yaml index f5bd4b66121..86874f8d1a6 100644 --- a/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.yaml +++ b/src/site/antora/modules/ROOT/examples/manual/appenders/database/nosql-mongo.yaml @@ -24,7 +24,9 @@ Configuration: NoSql: name: "MONGO" MongoDb: - connection: "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/logging.logs" + connection: "mongodb://${env:DB_USER}:${env:DB_PASS}@localhost:27017/" + databaseName: "logging" + collectionName: "logs" # end::appender[] Loggers: # tag::loggers[] diff --git a/src/site/antora/modules/ROOT/pages/manual/appenders/database.adoc b/src/site/antora/modules/ROOT/pages/manual/appenders/database.adoc index cf42f454d9a..901c37ba25a 100644 --- a/src/site/antora/modules/ROOT/pages/manual/appenders/database.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/appenders/database.adoc @@ -1164,14 +1164,32 @@ It supports the following configuration options: | Attribute | Type | Default value | Description | [[MongoDbProvider-attr-connection]]connection -| https://mongodb.github.io/mongo-java-driver/5.1/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html[`ConnectionString`] +| https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format[Connection String] | | It specifies the connection URI used to reach the server. -See -https://www.mongodb.com/docs/drivers/java/sync/current/fundamentals/connection/connect/#connection-uri[Connection URI documentation] -for its format. +**Required** + +| [[MongoDbProvider-attr-databaseName]]databaseName +| `string` +| +| +It specifies the name of the database for the appender to use. + +The database name can also be specified in <>. +If both are provided, this `databaseName` attribute will be used. + +**Required** + +| [[MongoDbProvider-attr-collectionName]]collectionName +| `string` +| +| +It specifies the name of the collection for the appender to use. + +For backward compatibility, the collection name can also be specified in <> per https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html[`ConnectionString` of the MongoDB Java Driver]. +If both are provided, this `collectionName` attribute will be used. **Required** @@ -1210,15 +1228,11 @@ It supports the following configuration attributes: | Attribute | Type | Default value | Description | [[MongoDb4Provider-attr-connection]]connection -| https://mongodb.github.io/mongo-java-driver/5.1/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html[`ConnectionString`] +| https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html[`ConnectionString`] | | It specifies the connection URI used to reach the server. -See -https://www.mongodb.com/docs/drivers/java/sync/current/fundamentals/connection/connect/#connection-uri[Connection URI documentation] -for its format. - **Required** | [[MongoDb4Provider-attr-capped]]capped From 14adc25a49825b49995d4070210e87a3660f0f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Fri, 21 Feb 2025 14:29:53 +0100 Subject: [PATCH 23/42] Add changelog entry (#3066) --- src/changelog/.2.x.x/3066_fix_bom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/changelog/.2.x.x/3066_fix_bom.xml diff --git a/src/changelog/.2.x.x/3066_fix_bom.xml b/src/changelog/.2.x.x/3066_fix_bom.xml new file mode 100644 index 00000000000..56c14c2d16b --- /dev/null +++ b/src/changelog/.2.x.x/3066_fix_bom.xml @@ -0,0 +1,9 @@ + + + + + Fix the leak of non-Log4j dependencies in `log4j-bom` + From 9bc402e442120dcfbb375919aaabab930bf6876d Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Mon, 24 Feb 2025 12:08:02 +0000 Subject: [PATCH 24/42] Update `fast-xml-parser` to version `5.0.6` (#3487) --- package.json | 2 +- src/changelog/.2.x.x/update_fast_xml_parser.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/update_fast_xml_parser.xml diff --git a/package.json b/package.json index df223e24ef1..c21bb9ed1b5 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "@antora/site-generator-default": "^3.2.0-alpha.4", "@asciidoctor/tabs": "^1.0.0-beta.6", "asciidoctor-kroki": "^0.18.1", - "fast-xml-parser": "^4.3.6", + "fast-xml-parser": "^5.0.6", "handlebars": "^4.7.8" } } diff --git a/src/changelog/.2.x.x/update_fast_xml_parser.xml b/src/changelog/.2.x.x/update_fast_xml_parser.xml new file mode 100644 index 00000000000..ad03215ba08 --- /dev/null +++ b/src/changelog/.2.x.x/update_fast_xml_parser.xml @@ -0,0 +1,8 @@ + + + + Update `fast-xml-parser` to version `5.0.6` + From bf6ef23f0b22220f36720298811c760bc2c3cc8e Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Mon, 24 Feb 2025 12:18:26 +0000 Subject: [PATCH 25/42] Update `org.junit:junit-bom` to version `5.12.0` (#3488) --- log4j-parent/pom.xml | 2 +- src/changelog/.2.x.x/update_org_junit_junit_bom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 8a8d9de2397..d7067eabb67 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -109,7 +109,7 @@ 3.6.0 1.37 4.13.2 - 5.11.4 + 5.12.0 1.9.1 3.9.0 0.2.0 diff --git a/src/changelog/.2.x.x/update_org_junit_junit_bom.xml b/src/changelog/.2.x.x/update_org_junit_junit_bom.xml index 423686ebd94..cd7a3ddf98b 100644 --- a/src/changelog/.2.x.x/update_org_junit_junit_bom.xml +++ b/src/changelog/.2.x.x/update_org_junit_junit_bom.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - - Update `org.junit:junit-bom` to version `5.11.4` + + Update `org.junit:junit-bom` to version `5.12.0` From 6f4fab988f0c96dd3f636a570a8c665532a1c7f8 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Mon, 24 Feb 2025 12:21:40 +0000 Subject: [PATCH 26/42] Update `org.awaitility:awaitility` to version `4.3.0` (#3489) --- log4j-parent/pom.xml | 2 +- src/changelog/.2.x.x/update_org_awaitility_awaitility.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/update_org_awaitility_awaitility.xml diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index d7067eabb67..1f2bb262084 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -64,7 +64,7 @@ 2.0.2 2.0.3 3.27.3 - 4.2.2 + 4.3.0 2.0b6 7.1.0 3.11.19 diff --git a/src/changelog/.2.x.x/update_org_awaitility_awaitility.xml b/src/changelog/.2.x.x/update_org_awaitility_awaitility.xml new file mode 100644 index 00000000000..00746c8e473 --- /dev/null +++ b/src/changelog/.2.x.x/update_org_awaitility_awaitility.xml @@ -0,0 +1,8 @@ + + + + Update `org.awaitility:awaitility` to version `4.3.0` + From dde535f4c405467100508484c5d9ad7cb0564f5e Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 26 Feb 2025 11:21:24 +0000 Subject: [PATCH 27/42] Update `org.slf4j:slf4j-nop` to version `2.0.17` (#3496) --- log4j-mongodb/pom.xml | 2 +- src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-mongodb/pom.xml b/log4j-mongodb/pom.xml index 822f4d382db..2d45d209fec 100644 --- a/log4j-mongodb/pom.xml +++ b/log4j-mongodb/pom.xml @@ -31,7 +31,7 @@ org.apache.logging.log4j.core 5.3.1 - 2.0.16 + 2.0.17 diff --git a/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml b/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml index 2083a3efd68..9c4f22681eb 100644 --- a/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml +++ b/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - - Update `org.slf4j:slf4j-nop` to version `2.0.16` + + Update `org.slf4j:slf4j-nop` to version `2.0.17` From 56d14a4d17ad1a9ce7534d80e54512cf38a4ddb4 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 26 Feb 2025 11:21:58 +0000 Subject: [PATCH 28/42] Update `org.slf4j:slf4j-nop` to version `2.0.17` (#3490) --- log4j-mongodb4/pom.xml | 2 +- src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/log4j-mongodb4/pom.xml b/log4j-mongodb4/pom.xml index 9115fe4175f..753375bf3c1 100644 --- a/log4j-mongodb4/pom.xml +++ b/log4j-mongodb4/pom.xml @@ -36,7 +36,7 @@ org.apache.logging.log4j.core 4.11.5 - 2.0.16 + 2.0.17 diff --git a/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml b/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml index 9c4f22681eb..534c7b98844 100644 --- a/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml +++ b/src/changelog/.2.x.x/update_org_slf4j_slf4j_nop.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - + Update `org.slf4j:slf4j-nop` to version `2.0.17` From ca14c951c99e7c1e2129134a5f1fa664270f225b Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 26 Feb 2025 11:24:45 +0000 Subject: [PATCH 29/42] Update `org.slf4j:slf4j-api` to version `2.0.17` (#3492) --- log4j-core-its/pom.xml | 2 +- src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml diff --git a/log4j-core-its/pom.xml b/log4j-core-its/pom.xml index 49f770b883c..e657b86c210 100644 --- a/log4j-core-its/pom.xml +++ b/log4j-core-its/pom.xml @@ -40,7 +40,7 @@ true - 2.0.16 + 2.0.17 diff --git a/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml b/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml new file mode 100644 index 00000000000..a8f0ac58632 --- /dev/null +++ b/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml @@ -0,0 +1,8 @@ + + + + Update `org.slf4j:slf4j-api` to version `2.0.17` + From 2d08264a247148a4292ae9d9bb5a4938459f0756 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 26 Feb 2025 11:26:07 +0000 Subject: [PATCH 30/42] Update `org.slf4j:slf4j-api` to version `2.0.17` (#3497) --- pom.xml | 2 +- src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1fb7338f7ca..941e75b523c 100644 --- a/pom.xml +++ b/pom.xml @@ -352,7 +352,7 @@ 0.6.0 3.9.0 1.3.15 - 2.0.16 + 2.0.17 - 2.0.16 + 2.0.17 diff --git a/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml b/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml index 267d2aeb760..1a464ff212a 100644 --- a/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml +++ b/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - + Update `org.slf4j:slf4j-api` to version `2.0.17` From 727c9926285d6ac2fefbe5725ecb6a3b896920a8 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Wed, 26 Feb 2025 11:28:23 +0000 Subject: [PATCH 32/42] Update `org.slf4j:slf4j-api` to version `2.0.17` (#3499) --- log4j-slf4j2-impl/pom.xml | 2 +- src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/log4j-slf4j2-impl/pom.xml b/log4j-slf4j2-impl/pom.xml index 453a8f7ae35..f6adab82826 100644 --- a/log4j-slf4j2-impl/pom.xml +++ b/log4j-slf4j2-impl/pom.xml @@ -36,7 +36,7 @@ (Refer to the `log4j-to-slf4j` artifact for forwarding the Log4j API to SLF4J.) - 2.0.16 + 2.0.17 diff --git a/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml b/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml index 1a464ff212a..856135854c9 100644 --- a/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml +++ b/src/changelog/.2.x.x/update_org_slf4j_slf4j_api.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - + Update `org.slf4j:slf4j-api` to version `2.0.17` From d8cbe77a0a1f60e6708a2bd8f84ea2fb8666c95a Mon Sep 17 00:00:00 2001 From: JWT Date: Thu, 27 Feb 2025 13:25:15 +0100 Subject: [PATCH 33/42] Bugfix/log4 j 3359 2 (#3502) * Fixed TypeConverters#LevelConverter javadoc (#3359) * Moved changelog to .2.x.x per PR Code Review (#3359) --------- Co-authored-by: Jeff Thomas --- .../core/config/plugins/convert/TypeConverters.java | 9 ++++++++- src/changelog/.2.x.x/3359_fix-javadoc.xml | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/3359_fix-javadoc.xml diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java index 3e005f07c5d..6c81224c2d6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java @@ -285,10 +285,17 @@ public Integer convert(final String s) { } /** - * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names. + * Converts a {@link String} into a Log4j {@link Level}. */ @Plugin(name = "Level", category = CATEGORY) public static class LevelConverter implements TypeConverter { + /** + * {@inheritDoc} + * @param s the string to convert + * @return the resolved level + * @throws NullPointerException if the given value is {@code null}. + * @throws IllegalArgumentException if the given argument is not resolvable to a level + */ @Override public Level convert(final String s) { return Level.valueOf(s); diff --git a/src/changelog/.2.x.x/3359_fix-javadoc.xml b/src/changelog/.2.x.x/3359_fix-javadoc.xml new file mode 100644 index 00000000000..4e0a3dffb7e --- /dev/null +++ b/src/changelog/.2.x.x/3359_fix-javadoc.xml @@ -0,0 +1,10 @@ + + + + + TypeConverters convert for "Level" incorrectly documented behaviour for invalid value - updated javadoc. + + From 1da1d7e1023e8d47e89f065ef79cc937294aafe3 Mon Sep 17 00:00:00 2001 From: ASF Logging Services RM Date: Fri, 28 Feb 2025 11:07:25 +0000 Subject: [PATCH 34/42] Update `org.apache.groovy:groovy-bom` to version `4.0.26` (#3506) --- log4j-parent/pom.xml | 2 +- src/changelog/.2.x.x/update_org_apache_groovy_groovy_bom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 1f2bb262084..ca4a63b3680 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -81,7 +81,7 @@ 3.4.4 0.9.0 7.0.5 - 4.0.25 + 4.0.26 33.4.0-jre 2.2.224 3.0 diff --git a/src/changelog/.2.x.x/update_org_apache_groovy_groovy_bom.xml b/src/changelog/.2.x.x/update_org_apache_groovy_groovy_bom.xml index ff3bc0aa164..a1dffb7aba0 100644 --- a/src/changelog/.2.x.x/update_org_apache_groovy_groovy_bom.xml +++ b/src/changelog/.2.x.x/update_org_apache_groovy_groovy_bom.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="updated"> - - Update `org.apache.groovy:groovy-bom` to version `4.0.25` + + Update `org.apache.groovy:groovy-bom` to version `4.0.26` From 8d05a73372a8f5fcc9df5f3c12413179174e7fb5 Mon Sep 17 00:00:00 2001 From: JWT Date: Sat, 1 Mar 2025 20:49:17 +0100 Subject: [PATCH 35/42] Fixed AbstractFilterable#isFiltered javadoc (#3300) (#3456) * Fixed AbstractFilterable#isFiltered javadoc (#3300) * Moved changelog to .2.x.x per PR Code Review (#3300) * Fixed AbstractFilterable#isFiltered javadoc (#3300) * Moved changelog to .2.x.x per PR Code Review (#3300) --------- Co-authored-by: Jeff Thomas --- .../logging/log4j/core/filter/AbstractFilterable.java | 3 ++- src/changelog/.2.x.x/3300_fix-javadoc.xml | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/3300_fix-javadoc.xml diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java index 138ec820cae..74f0790ad3b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java @@ -145,7 +145,8 @@ public boolean hasFilter() { /** * Determine if the LogEvent should be processed or ignored. * @param event The LogEvent. - * @return true if the LogEvent should be processed. + * @return {@code true} if the event is filtered and should be ignored; otherwise, {@code false} if + * it should be processed */ @Override public boolean isFiltered(final LogEvent event) { diff --git a/src/changelog/.2.x.x/3300_fix-javadoc.xml b/src/changelog/.2.x.x/3300_fix-javadoc.xml new file mode 100644 index 00000000000..5d84b4325cb --- /dev/null +++ b/src/changelog/.2.x.x/3300_fix-javadoc.xml @@ -0,0 +1,10 @@ + + + + + Corrected @return javadoc for AbstractFilterable#isFiltered(). + + From ffbdda56a517b6ada137a41464d07b8a10b5f2f6 Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Wed, 12 Feb 2025 14:07:22 +0100 Subject: [PATCH 36/42] Removed 'patternFlags' @PluginAttribute from RegexFilter @PluginFactory createFilter. (#3086) --- .../log4j/core/filter/RegexFilter.java | 77 +++++++++++-------- ...remove_patternflags_from_PluginFactory.xml | 10 +++ 2 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml 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..84cdbfad47b 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,6 @@ */ package org.apache.logging.log4j.core.filter; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.logging.log4j.Level; @@ -29,7 +26,6 @@ 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.message.Message; import org.apache.logging.log4j.message.MessageFormatMessage; @@ -43,7 +39,6 @@ @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) public final class RegexFilter extends AbstractFilter { - private static final int DEFAULT_PATTERN_FLAGS = 0; private final Pattern pattern; private final boolean useRawMessage; @@ -110,10 +105,7 @@ private Result filter(final String msg) { @Override public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append("useRaw=").append(useRawMessage); - sb.append(", pattern=").append(pattern.toString()); - return sb.toString(); + return "useRaw=" + useRawMessage + ", pattern=" + pattern.toString(); } /** @@ -123,6 +115,40 @@ public String toString() { * The regular expression to match. * @param patternFlags * An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag. + * (no longer used - pattern flags can be embedded in regex-expression. + * @param useRawMsg + * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. + * @param match + * The action to perform when a match occurs. + * @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. + * @deprecated use {@link #createFilter(String, Boolean, Result, Result)} + */ + @Deprecated + // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder + public static RegexFilter createFilter( + // @formatter:off + @PluginAttribute("regex") final String regex, + final String[] patternFlags, + @PluginAttribute("useRawMsg") final Boolean useRawMsg, + @PluginAttribute("onMatch") final Result match, + @PluginAttribute("onMismatch") final Result mismatch) + // @formatter:on + throws IllegalArgumentException, IllegalAccessException { + + // LOG4J-3086 - pattern-flags can be embedded in RegEx expression + + return createFilter(regex, useRawMsg, match, mismatch); + } + + /** + * Creates a Filter that matches a regular expression. + * + * @param regex + * The regular expression to match. * @param useRawMsg * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. * @param match @@ -138,40 +164,23 @@ public String toString() { public static RegexFilter createFilter( // @formatter:off @PluginAttribute("regex") final String regex, - @PluginElement("PatternFlags") final String[] patternFlags, @PluginAttribute("useRawMsg") final Boolean useRawMsg, @PluginAttribute("onMatch") final Result match, @PluginAttribute("onMismatch") final Result mismatch) // @formatter:on throws IllegalArgumentException, IllegalAccessException { + boolean raw = Boolean.TRUE.equals(useRawMsg); if (regex == null) { LOGGER.error("A regular expression must be provided for RegexFilter"); return null; } - return new RegexFilter( - Boolean.TRUE.equals(useRawMsg), Pattern.compile(regex, toPatternFlags(patternFlags)), match, mismatch); - } - - private static int toPatternFlags(final String[] patternFlags) - throws IllegalArgumentException, IllegalAccessException { - if (patternFlags == null || patternFlags.length == 0) { - 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(); - } - 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); - } + final Pattern pattern; + try { + pattern = Pattern.compile(regex); + } catch (final Exception ex) { + LOGGER.error("Unable to compile regular expression: {}", regex, ex); + return null; } - return flags; + return new RegexFilter(raw, pattern, match, mismatch); } } diff --git a/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml b/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml new file mode 100644 index 00000000000..0e61653f85c --- /dev/null +++ b/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml @@ -0,0 +1,10 @@ + + + + + Removed 'patternFlags' @PluginAttribute from RegexFilter @PluginFactory createFilter. + + From d5d7aed754861bfca05d0487aa9981a9237f1cef Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Mon, 17 Feb 2025 17:05:26 +0100 Subject: [PATCH 37/42] Updates per PR Code Review (#3086) + made AbstractFiltter.AbstractFilterBuilder onMatch/onMismatch fields protected + added AbstractFilter(AbstractFilterBuilder) constructor + added RegexFilter.Builder implementation + added RegexFilter(Builder) constructor + moved RegexFilter Pattern compile into constructor + added fields to persist configuration propertties + getters (regexExpression, patternFlags) + changed private constructor to accept builder as argument + renamed private method 'targetMessageTest' to more approprriate 'getMessageTextByType' + added Javadoc + grouped deprecations --- .../log4j/core/filter/RegexFilterTest.java | 11 +- .../log4j/core/filter/AbstractFilter.java | 33 +- .../log4j/core/filter/RegexFilter.java | 358 ++++++++++++++---- .../logging/log4j/core/util/Builder.java | 4 + ...remove_patternflags_from_PluginFactory.xml | 0 5 files changed, 328 insertions(+), 78 deletions(-) rename src/changelog/{2.25.0 => .2.x.x}/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml (100%) 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..e5801342dd6 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; @@ -43,13 +44,17 @@ static void before() { @Test void testRegexFilterDoesNotThrowWithAllTheParametersExceptRegexEqualNull() { assertDoesNotThrow(() -> { - RegexFilter.createFilter(".* test .*", null, null, null, null); + RegexFilter.newBuilder().setRegex(".* test .*").build(); }); } @Test void testThresholds() throws Exception { - RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null); + RegexFilter filter = RegexFilter.newBuilder() + .setRegex(".* test .*") + .setUseRawMsg(false) + .build(); + assertNotNull(filter); filter.start(); assertTrue(filter.isStarted()); assertSame( @@ -65,7 +70,7 @@ void testThresholds() throws Exception { .setMessage(new SimpleMessage("test")) // .build(); assertSame(Filter.Result.DENY, filter.filter(event)); - filter = RegexFilter.createFilter(null, null, false, null, null); + filter = RegexFilter.newBuilder().build(); assertNull(filter); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java index 397390bcbc3..05b1e6b275b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.core.filter; +import java.util.Objects; +import java.util.Optional; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.AbstractLifeCycle; @@ -43,16 +45,30 @@ public abstract static class AbstractFilterBuilder builder) { + + Objects.requireNonNull(builder, "The 'builder' argument cannot be null."); + + this.onMatch = Optional.ofNullable(builder.onMatch).orElse(Result.NEUTRAL); + this.onMismatch = Optional.ofNullable(builder.onMismatch).orElse(Result.DENY); + } + @Override protected boolean equalsImpl(final Object obj) { if (this == obj) { 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 84cdbfad47b..d26d157bcc7 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,7 +16,10 @@ */ package org.apache.logging.log4j.core.filter; -import java.util.regex.Matcher; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; import java.util.regex.Pattern; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -26,7 +29,9 @@ 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.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.PluginElement; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFormatMessage; import org.apache.logging.log4j.message.ParameterizedMessage; @@ -39,53 +44,132 @@ @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) public final class RegexFilter extends AbstractFilter { + /** The regular-expression. */ + private final String regex; + + /** The pattern compiled from the regular-expression. */ private final Pattern pattern; + + /** Flag: if {@code true} use message format-pattern / field for the match target. */ private final boolean useRawMessage; - private RegexFilter(final boolean raw, final Pattern pattern, final Result onMatch, final Result onMismatch) { - super(onMatch, onMismatch); - this.pattern = pattern; - this.useRawMessage = raw; + /** + * Constructs a new {@code RegexFilter} configured by the given builder. + * @param builder the builder + * @throws IllegalArgumentException if the regular expression cannot be compiled to a pattern + */ + private RegexFilter(final Builder builder) { + + super(builder); + + this.regex = builder.regex; + this.useRawMessage = Boolean.TRUE.equals(builder.useRawMsg); + + try { + this.pattern = Pattern.compile(regex); + } catch (final Exception ex) { + throw new IllegalArgumentException("Unable to compile regular expression: '" + regex + "'.", ex); + } + } + + /** + * Returns the regular-expression. + * @return the regular-expression (it may be an empty string but never {@code null}) + */ + public String getRegex() { + return this.regex; + } + + /** + * Returns the compiled regular-expression pattern. + * @return the pattern (will never be {@code null} + */ + public Pattern getPattern() { + return this.pattern; + } + + /** + * Returns whether the raw-message should be used. + * @return {@code} if the raw message should be used; otherwise, {@code false} + */ + public boolean isUseRawMessage() { + return this.useRawMessage; } + /** {@inheritDoc} */ @Override public Result filter( final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { - if (useRawMessage || params == null || params.length == 0) { - return filter(msg); - } - return filter(ParameterizedMessage.format(msg, params)); + return (useRawMessage || params == null || params.length == 0) + ? filter(msg) + : filter(ParameterizedMessage.format(msg, params)); } + /** {@inheritDoc} */ @Override public Result filter( final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) { - if (msg == null) { - return onMismatch; - } - return filter(msg.toString()); + return (msg == null) ? this.onMismatch : filter(msg.toString()); } + /** {@inheritDoc} */ @Override public Result filter( final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) { if (msg == null) { return onMismatch; } - final String text = targetMessageTest(msg); - return filter(text); + return filter(getMessageTextByType(msg)); } + /** {@inheritDoc} */ @Override public Result filter(final LogEvent event) { - final String text = targetMessageTest(event.getMessage()); - return filter(text); + return filter(getMessageTextByType(event.getMessage())); + } + + /** + * Apply the filter to the given message and return the match/mismatch result. + *

+ * If the given '{@code msg}' is {@code null} the configured mismatch result will be returned. + *

+ * @param msg the message + * @return the filter result + */ + private Result filter(final String msg) { + if (msg == null) { + return onMismatch; + } + return pattern.matcher(msg).matches() ? onMatch : onMismatch; } - // While `Message#getFormat()` is broken in general, it still makes sense for certain types. - // Hence, suppress the deprecation warning. + /** + * Tests the filter pattern against the given Log4j {@code Message}. + *

+ * If the raw-message flag is enabled and message is an instance of the following, the raw message format + * will be returned. + *

+ *
    + *
  • {@link MessageFormatMessage}
  • + *
  • {@link ParameterizedMessage}
  • + *
  • {@link StringFormattedMessage}
  • + *
  • {@link StructuredDataMessage}
  • + *
+ *

+ * If the '{@code useRawMessage}' flag is disabled OR the message is not one of the above + * implementations, the message's formatted message will be returned. + *

+ *

Developer Note

+ *

+ * While `Message#getFormat()` is broken in general, it still makes sense for certain types. + * Hence, suppress the deprecation warning. + *

+ * + * @param message the message + * @return the target message based on configuration and message-type + */ @SuppressWarnings("deprecation") - private String targetMessageTest(final Message message) { + private String getMessageTextByType(final Message message) { return useRawMessage && (message instanceof ParameterizedMessage || message instanceof StringFormattedMessage @@ -95,92 +179,220 @@ private String targetMessageTest(final Message message) { : message.getFormattedMessage(); } - private Result filter(final String msg) { - if (msg == null) { - return onMismatch; - } - final Matcher m = pattern.matcher(msg); - return m.matches() ? onMatch : onMismatch; - } - @Override public String toString() { - return "useRaw=" + useRawMessage + ", pattern=" + pattern.toString(); + return "useRawMessage=" + useRawMessage + ", regex=" + regex + ", pattern=" + pattern.toString(); } /** - * Creates a Filter that matches a regular expression. + * Creates a new builder instance. * - * @param regex - * The regular expression to match. - * @param patternFlags - * An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag. - * (no longer used - pattern flags can be embedded in regex-expression. - * @param useRawMsg - * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. - * @param match - * The action to perform when a match occurs. - * @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. - * @deprecated use {@link #createFilter(String, Boolean, Result, Result)} + * @return the new builder instance + */ + @PluginBuilderFactory + public static RegexFilter.Builder newBuilder() { + return new RegexFilter.Builder(); + } + + /** + * A {@link RegexFilter} builder instance. + */ + public static final class Builder extends AbstractFilterBuilder + implements org.apache.logging.log4j.core.util.Builder { + + /* NOTE: LOG4J-3086 - No patternFlags in builder - this functionality has been deprecated/removed. */ + + /** + * The regular expression to match. + */ + @PluginBuilderAttribute + private String regex; + + /** + * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, + * and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, + * the message field will be used as the match target. + */ + @PluginBuilderAttribute + private Boolean useRawMsg; + + /** + * Private constructor. + */ + private Builder() { + super(); + } + + /** + * Sets the regular-expression. + * + * @param regex the regular-expression + * @return this builder + */ + public Builder setRegex(final String regex) { + this.regex = regex; + return this; + } + + /** + * Sets the use raw msg flag. + * + * @param useRawMsg {@code true} if the message format-patter/field will be used as match target; + * otherwise, {@code false} + * @return this builder + */ + public Builder setUseRawMsg(final boolean useRawMsg) { + this.useRawMsg = useRawMsg; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isValid() { + return (regex != null); + } + + /** + * Builds and returns a {@link RegexFilter} instance configured by this builder. + * + * @return the created {@link RegexFilter} or {@code null} if the builder is misconfigured + */ + @Override + public RegexFilter build() { + + if (!isValid()) { + return null; + } + + try { + return new RegexFilter(this); + } catch (final Exception ex) { + LOGGER.error("Unable to create RegexFilter. {}", ex.getMessage(), ex); + return null; + } + } + } + + /* + * DEPRECATIONS: + * The constructor/fields/methods below have been deprecated. + * - the 'create***' factory methods should no longer be used - use the builder instead + * - pattern-flags should now be passed via the regular expression itself + */ + + /** + * @deprecated pattern flags have been deprecated - they can just be included in the regex-expression. */ @Deprecated - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - public static RegexFilter createFilter( - // @formatter:off - @PluginAttribute("regex") final String regex, - final String[] patternFlags, - @PluginAttribute("useRawMsg") final Boolean useRawMsg, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) - // @formatter:on - throws IllegalArgumentException, IllegalAccessException { + private static final int DEFAULT_PATTERN_FLAGS = 0; - // LOG4J-3086 - pattern-flags can be embedded in RegEx expression + /** + * @deprecated - pattern flags no longer supported. + */ + @Deprecated + private String[] patternFlags = new String[0]; - return createFilter(regex, useRawMsg, match, mismatch); + /** + * @deprecated use {@link RegexFilter.Builder} instead + */ + @Deprecated + @SuppressWarnings("MagicConstant") + private RegexFilter( + final boolean useRawMessage, + final String regex, + final String[] patternFlags, + final Result onMatch, + final Result onMismatch) { + super(onMatch, onMismatch); + this.regex = Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter"); + this.patternFlags = patternFlags == null ? new String[0] : patternFlags.clone(); + try { + int flags = toPatternFlags(this.patternFlags); + this.pattern = Pattern.compile(regex, flags); + } catch (final Exception ex) { + throw new IllegalArgumentException("Unable to compile regular expression: '" + regex + "'.", ex); + } + this.useRawMessage = useRawMessage; + } + + /** + * Returns the pattern-flags applied to the regular-expression when compiling the pattern. + * + * @return the pattern-flags (maybe empty but never {@code null} + * @deprecated pattern-flags are no longer supported + */ + @Deprecated + public String[] getPatternFlags() { + return this.patternFlags.clone(); } /** * Creates a Filter that matches a regular expression. * - * @param regex - * The regular expression to match. - * @param useRawMsg - * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target. - * @param match - * The action to perform when a match occurs. - * @param mismatch - * The action to perform when a mismatch occurs. + * @param regex The regular expression to match. + * @param patternFlags An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag. + * (no longer used - pattern flags can be embedded in regex-expression. + * @param useRawMsg If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, + * and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, + * the message field will be used as the match target. + * @param match The action to perform when a match occurs. + * @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 IllegalAccessException When there is no access to the definition of the specified member. * @throws IllegalArgumentException When passed an illegal or inappropriate argument. + * @deprecated use {@link #newBuilder} to instantiate builder */ - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - @PluginFactory + @Deprecated public static RegexFilter createFilter( // @formatter:off @PluginAttribute("regex") final String regex, + @PluginElement("PatternFlags") final String[] patternFlags, @PluginAttribute("useRawMsg") final Boolean useRawMsg, @PluginAttribute("onMatch") final Result match, @PluginAttribute("onMismatch") final Result mismatch) // @formatter:on throws IllegalArgumentException, IllegalAccessException { + + // LOG4J-3086 - pattern-flags can be embedded in RegEx expression + boolean raw = Boolean.TRUE.equals(useRawMsg); if (regex == null) { LOGGER.error("A regular expression must be provided for RegexFilter"); return null; } - final Pattern pattern; + try { - pattern = Pattern.compile(regex); + return new RegexFilter(raw, regex, patternFlags, match, mismatch); } catch (final Exception ex) { - LOGGER.error("Unable to compile regular expression: {}", regex, ex); + LOGGER.error("Unable to create RegexFilter. {}", ex.getMessage(), ex); return null; } - return new RegexFilter(raw, pattern, match, mismatch); + } + + /** @deprecated pattern flags have been deprecated - they can just be included in the regex-expression. */ + @Deprecated + private static int toPatternFlags(final String[] patternFlags) + throws IllegalArgumentException, IllegalAccessException { + if (patternFlags == null || patternFlags.length == 0) { + 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(); + } + 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); + } + } + return flags; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java index 10bc1a9f52e..8a772dc7330 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java @@ -44,6 +44,10 @@ public interface Builder { */ T build(); + /** + * Validates that the builder is properly configured to build. + * @return {@code true} if the builder configuration is valid; otherwise, {@code false} + */ default boolean isValid() { return PluginBuilder.validateFields(this, getErrorPrefix()); } diff --git a/src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml b/src/changelog/.2.x.x/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml similarity index 100% rename from src/changelog/2.25.0/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml rename to src/changelog/.2.x.x/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml From 6e7e82dbd7d810ae98c68a0880b30504ebad3887 Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Mon, 17 Feb 2025 17:47:17 +0100 Subject: [PATCH 38/42] A few more improvements + added tests (#3086) --- .../log4j/core/filter/RegexFilterTest.java | 126 +++++++++++++++--- .../log4j/core/filter/RegexFilter.java | 42 ++++-- 2 files changed, 142 insertions(+), 26 deletions(-) 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 e5801342dd6..6cb07a5f7df 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,9 +19,12 @@ 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.logging.log4j.Level; @@ -87,9 +90,18 @@ void testDotAllPattern() throws Exception { } @Test - void testNoMsg() throws Exception { - final RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null); + void testNoMsg() { + + final RegexFilter filter = + RegexFilter.newBuilder() + .setRegex(".* test .*") + .setUseRawMsg(false) + .build(); + + assertNotNull(filter); + filter.start(); + assertTrue(filter.isStarted()); assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null)); assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Message) null, null)); @@ -97,28 +109,112 @@ void testNoMsg() throws Exception { } @Test - void testParameterizedMsg() throws Exception { + void testParameterizedMsg() { final String msg = "params {} {}"; final Object[] params = {"foo", "bar"}; // match against raw message - final RegexFilter rawFilter = RegexFilter.createFilter( - "params \\{\\} \\{\\}", - null, - true, // useRawMsg - Result.ACCEPT, - Result.DENY); + final RegexFilter rawFilter = + RegexFilter.newBuilder() + .setRegex("params \\{\\} \\{\\}") + .setUseRawMsg(true) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY) + .build(); + + assertNotNull(rawFilter); + final Result rawResult = rawFilter.filter(null, null, null, msg, params); assertThat(rawResult, equalTo(Result.ACCEPT)); // match against formatted message - final RegexFilter fmtFilter = RegexFilter.createFilter( - "params foo bar", - null, - false, // useRawMsg - Result.ACCEPT, - Result.DENY); + final RegexFilter fmtFilter = + RegexFilter.newBuilder() + .setRegex("params foo bar") + .setUseRawMsg(false) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY).build(); + + assertNotNull(fmtFilter); + final Result fmtResult = fmtFilter.filter(null, null, null, msg, params); assertThat(fmtResult, equalTo(Result.ACCEPT)); } + + /** + * A builder with no 'regex' expression should both be invalid and return null on 'build()'. + */ + @Test + void testWithValidRegex() { + + final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores + + final RegexFilter.Builder builder = + RegexFilter.newBuilder().setRegex(regex).setUseRawMsg(false).setOnMatch(Result.ACCEPT).setOnMismatch(Result.DENY); + + assertTrue(builder.isValid()); + + final RegexFilter filter = builder.build(); + + assertNotNull(filter); + + assertEquals(Result.ACCEPT, filter.filter("Hello_123")); + + assertEquals(Result.DENY, filter.filter("Hello@123")); + + assertEquals(regex, filter.getRegex()); + } + + @Test + void testRegexFilterGetters() { + + final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores + + final RegexFilter filter = + RegexFilter.newBuilder() + .setRegex(regex) + .setUseRawMsg(false) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY) + .build(); + + assertNotNull(filter); + + assertEquals(regex, filter.getRegex()); + assertFalse(filter.isUseRawMessage()); + assertEquals(Result.ACCEPT, filter.getOnMatch()); + assertEquals(Result.DENY, filter.getOnMismatch()); + assertNotNull(filter.getPattern()); + assertEquals(regex, filter.getPattern().pattern()); + } + + /** + * A builder with no 'regex' expression should both be invalid and return null on 'build()'. + */ + @Test + void testBuilderWithoutRegexNotValid() { + + final RegexFilter.Builder builder = RegexFilter.newBuilder(); + + assertFalse(builder.isValid()); + + assertNull(builder.build()); + + } + + /** + * A builder with an invalid 'regex' expression should return null on 'build()'. + */ + @Test + void testBuilderWithInvalidRegexNotValid() { + + final RegexFilter.Builder builder = RegexFilter.newBuilder(); + + builder.setRegex("[a-z"); + + assertFalse(builder.isValid()); + + assertNull(builder.build()); + + } } 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 d26d157bcc7..6c842e66593 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 @@ -21,6 +21,7 @@ import java.util.Comparator; import java.util.Objects; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; @@ -44,9 +45,6 @@ @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) public final class RegexFilter extends AbstractFilter { - /** The regular-expression. */ - private final String regex; - /** The pattern compiled from the regular-expression. */ private final Pattern pattern; @@ -62,13 +60,12 @@ private RegexFilter(final Builder builder) { super(builder); - this.regex = builder.regex; this.useRawMessage = Boolean.TRUE.equals(builder.useRawMsg); try { - this.pattern = Pattern.compile(regex); + this.pattern = Pattern.compile(builder.regex); } catch (final Exception ex) { - throw new IllegalArgumentException("Unable to compile regular expression: '" + regex + "'.", ex); + throw new IllegalArgumentException("Unable to compile regular expression: '" + builder.regex + "'.", ex); } } @@ -77,7 +74,7 @@ private RegexFilter(final Builder builder) { * @return the regular-expression (it may be an empty string but never {@code null}) */ public String getRegex() { - return this.regex; + return this.pattern.pattern(); } /** @@ -136,7 +133,7 @@ public Result filter(final LogEvent event) { * @param msg the message * @return the filter result */ - private Result filter(final String msg) { + public Result filter(final String msg) { if (msg == null) { return onMismatch; } @@ -181,7 +178,7 @@ private String getMessageTextByType(final Message message) { @Override public String toString() { - return "useRawMessage=" + useRawMessage + ", regex=" + regex + ", pattern=" + pattern.toString(); + return "useRawMessage=" + useRawMessage + ", pattern=" + pattern.toString(); } /** @@ -251,7 +248,11 @@ public Builder setUseRawMsg(final boolean useRawMsg) { */ @Override public boolean isValid() { - return (regex != null); + boolean valid = true; + if (!isRegexValid()) { + valid = false; + } + return valid; } /** @@ -273,6 +274,25 @@ public RegexFilter build() { return null; } } + + /** + * Validates the 'regex' attribute. + *

+ * If the regular-expression is not set, or cannot be compiled to a valid pattern the validation will fail. + *

+ * @return {@code true} if the regular-expression is valid; otherwise, {@code false} + */ + private boolean isRegexValid() { + if (regex == null) { + return false; + } + try { + Pattern.compile(regex); + } catch (final PatternSyntaxException ex) { + return false; + } + return true; + } } /* @@ -306,7 +326,7 @@ private RegexFilter( final Result onMatch, final Result onMismatch) { super(onMatch, onMismatch); - this.regex = Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter"); + Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter"); this.patternFlags = patternFlags == null ? new String[0] : patternFlags.clone(); try { int flags = toPatternFlags(this.patternFlags); From 283dcfffc09534d5bd3fd950a688929193114947 Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Sun, 2 Mar 2025 16:11:09 +0100 Subject: [PATCH 39/42] Fix validation behavior in RegexFilter (#3086) + added validation checks to RegexFilter + added JVerify nullability annotations to RegexFilter + updated javadoc + replaced deprecated usages of CompositeFilter#getFilters with CompositeFilter#getFiltersArray in AbstractFilterableTest --- .../log4j/core/filter/AbstractFilterTest.java | 1 - .../core/filter/AbstractFilterableTest.java | 24 +- .../log4j/core/filter/RegexFilterTest.java | 57 +++-- .../log4j/core/filter/RegexFilter.java | 217 ++++++++++-------- 4 files changed, 159 insertions(+), 140 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java index 8996b9f2f16..fc42abe2937 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java @@ -34,7 +34,6 @@ class AbstractFilterTest { @Test void testUnrolledBackwardsCompatible() { final ConcreteFilter filter = new ConcreteFilter(); - final Filter.Result expected = Filter.Result.DENY; verifyMethodsWithUnrolledVarargs(filter, Filter.Result.DENY); filter.testResult = Filter.Result.ACCEPT; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java index 4f117a1ccf5..bd3d3f51b20 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java @@ -54,7 +54,7 @@ void testAddMultipleSimpleFilters() { // into a CompositeFilter.class filterable.addFilter(filter); assertInstanceOf(CompositeFilter.class, filterable.getFilter()); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -67,7 +67,7 @@ void testAddMultipleEqualSimpleFilter() { // into a CompositeFilter.class filterable.addFilter(filter); assertInstanceOf(CompositeFilter.class, filterable.getFilter()); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -93,7 +93,7 @@ void testAddMultipleCompositeFilters() { // into a CompositeFilter.class filterable.addFilter(compositeFilter); assertInstanceOf(CompositeFilter.class, filterable.getFilter()); - assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -109,7 +109,7 @@ void testAddSimpleFilterAndCompositeFilter() { // into a CompositeFilter.class filterable.addFilter(compositeFilter); assertInstanceOf(CompositeFilter.class, filterable.getFilter()); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -125,7 +125,7 @@ void testAddCompositeFilterAndSimpleFilter() { // into a CompositeFilter.class filterable.addFilter(notInCompositeFilterFilter); assertInstanceOf(CompositeFilter.class, filterable.getFilter()); - assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -170,7 +170,7 @@ void testRemoveSimpleEqualFilterFromMultipleSimpleFilters() { filterable.addFilter(filterCopy); filterable.removeFilter(filterCopy); assertInstanceOf(CompositeFilter.class, filterable.getFilter()); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); filterable.removeFilter(filterCopy); assertEquals(filterOriginal, filterable.getFilter()); filterable.removeFilter(filterOriginal); @@ -224,7 +224,7 @@ void testRemoveSimpleFilterFromCompositeAndSimpleFilter() { // should not remove internal filter of compositeFilter filterable.removeFilter(anotherFilter); assertInstanceOf(CompositeFilter.class, filterable.getFilter()); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -247,9 +247,9 @@ void testRemoveFiltersFromComposite() { filterable.addFilter(compositeFilter); filterable.addFilter(anotherFilter); - assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); filterable.removeFilter(filter1); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); filterable.removeFilter(filter2); assertSame(anotherFilter, filterable.getFilter()); } @@ -274,11 +274,7 @@ public boolean equals(final Object o) { final EqualFilter that = (EqualFilter) o; - if (key != null ? !key.equals(that.key) : that.key != null) { - return false; - } - - return true; + return key != null ? key.equals(that.key) : that.key == null; } @Override 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 6cb07a5f7df..b7a128b5bfb 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 @@ -24,7 +24,6 @@ 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.logging.log4j.Level; @@ -92,11 +91,10 @@ void testDotAllPattern() throws Exception { @Test void testNoMsg() { - final RegexFilter filter = - RegexFilter.newBuilder() - .setRegex(".* test .*") - .setUseRawMsg(false) - .build(); + final RegexFilter filter = RegexFilter.newBuilder() + .setRegex(".* test .*") + .setUseRawMsg(false) + .build(); assertNotNull(filter); @@ -114,13 +112,12 @@ void testParameterizedMsg() { final Object[] params = {"foo", "bar"}; // match against raw message - final RegexFilter rawFilter = - RegexFilter.newBuilder() - .setRegex("params \\{\\} \\{\\}") - .setUseRawMsg(true) - .setOnMatch(Result.ACCEPT) - .setOnMismatch(Result.DENY) - .build(); + final RegexFilter rawFilter = RegexFilter.newBuilder() + .setRegex("params \\{\\} \\{\\}") + .setUseRawMsg(true) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY) + .build(); assertNotNull(rawFilter); @@ -128,12 +125,12 @@ void testParameterizedMsg() { assertThat(rawResult, equalTo(Result.ACCEPT)); // match against formatted message - final RegexFilter fmtFilter = - RegexFilter.newBuilder() - .setRegex("params foo bar") - .setUseRawMsg(false) - .setOnMatch(Result.ACCEPT) - .setOnMismatch(Result.DENY).build(); + final RegexFilter fmtFilter = RegexFilter.newBuilder() + .setRegex("params foo bar") + .setUseRawMsg(false) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY) + .build(); assertNotNull(fmtFilter); @@ -149,8 +146,11 @@ void testWithValidRegex() { final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores - final RegexFilter.Builder builder = - RegexFilter.newBuilder().setRegex(regex).setUseRawMsg(false).setOnMatch(Result.ACCEPT).setOnMismatch(Result.DENY); + final RegexFilter.Builder builder = RegexFilter.newBuilder() + .setRegex(regex) + .setUseRawMsg(false) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY); assertTrue(builder.isValid()); @@ -170,13 +170,12 @@ void testRegexFilterGetters() { final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores - final RegexFilter filter = - RegexFilter.newBuilder() - .setRegex(regex) - .setUseRawMsg(false) - .setOnMatch(Result.ACCEPT) - .setOnMismatch(Result.DENY) - .build(); + final RegexFilter filter = RegexFilter.newBuilder() + .setRegex(regex) + .setUseRawMsg(false) + .setOnMatch(Result.ACCEPT) + .setOnMismatch(Result.DENY) + .build(); assertNotNull(filter); @@ -199,7 +198,6 @@ void testBuilderWithoutRegexNotValid() { assertFalse(builder.isValid()); assertNull(builder.build()); - } /** @@ -215,6 +213,5 @@ void testBuilderWithInvalidRegexNotValid() { assertFalse(builder.isValid()); assertNull(builder.build()); - } } 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 6c842e66593..ffe470af485 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 @@ -21,7 +21,6 @@ import java.util.Comparator; import java.util.Objects; import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; @@ -33,16 +32,23 @@ 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.PluginElement; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.util.Assert; 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; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * A filter that matches the given regular expression pattern against messages. + * This filter returns the {@code onMatch} result if the message exactly matches the configured + * "{@code regex}" regular-expression pattern; otherwise, it returns the {@code onMismatch} result. */ @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@NullMarked public final class RegexFilter extends AbstractFilter { /** The pattern compiled from the regular-expression. */ @@ -54,12 +60,16 @@ public final class RegexFilter extends AbstractFilter { /** * Constructs a new {@code RegexFilter} configured by the given builder. * @param builder the builder - * @throws IllegalArgumentException if the regular expression cannot be compiled to a pattern + * @throws IllegalArgumentException if the regular expression is not configured or cannot be compiled to a pattern */ private RegexFilter(final Builder builder) { super(builder); + if (Strings.isNotBlank(builder.regex)) { + throw new IllegalArgumentException("The 'regex' attribute must not be null or empty."); + } + this.useRawMessage = Boolean.TRUE.equals(builder.useRawMsg); try { @@ -69,14 +79,6 @@ private RegexFilter(final Builder builder) { } } - /** - * Returns the regular-expression. - * @return the regular-expression (it may be an empty string but never {@code null}) - */ - public String getRegex() { - return this.pattern.pattern(); - } - /** * Returns the compiled regular-expression pattern. * @return the pattern (will never be {@code null} @@ -85,59 +87,123 @@ public Pattern getPattern() { return this.pattern; } + /** + * Returns the regular-expression. + * @return the regular-expression (it may be an empty string but never {@code null}) + */ + public String getRegex() { + return this.pattern.pattern(); + } + /** * Returns whether the raw-message should be used. - * @return {@code} if the raw message should be used; otherwise, {@code false} + * @return {@code true} if the raw message should be used; otherwise, {@code false} */ public boolean isUseRawMessage() { return this.useRawMessage; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with + * the given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

    + *
  • {@code logger}
  • + *
  • {@code level}
  • + *
  • {@code marker}
  • + *
+ *

+ */ @Override public Result filter( - final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { + final @Nullable Logger logger, + final @Nullable Level level, + final @Nullable Marker marker, + final @Nullable String msg, + final @Nullable Object @Nullable ... params) { + return (useRawMessage || params == null || params.length == 0) ? filter(msg) : filter(ParameterizedMessage.format(msg, params)); } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

    + *
  • {@code logger}
  • + *
  • {@code level}
  • + *
  • {@code marker}
  • + *
  • {@code throwable}
  • + *
+ *

+ */ @Override public Result filter( - final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) { - return (msg == null) ? this.onMismatch : filter(msg.toString()); + final @Nullable Logger logger, + final @Nullable Level level, + final @Nullable Marker marker, + final @Nullable Object message, + final @Nullable Throwable throwable) { + + return (message == null) ? this.onMismatch : filter(message.toString()); } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

    + *
  • {@code logger}
  • + *
  • {@code level}
  • + *
  • {@code marker}
  • + *
  • {@code throwable}
  • + *
+ *

+ */ @Override public Result filter( - final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) { - if (msg == null) { - return onMismatch; - } - return filter(getMessageTextByType(msg)); + final @Nullable Logger logger, + final @Nullable Level level, + final @Nullable Marker marker, + final @Nullable Message message, + final @Nullable Throwable throwable) { + return (message == null) ? this.onMismatch : filter(getMessageTextByType(message)); } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * + * @throws NullPointerException if the {@code event} argument is {@code null} + */ @Override public Result filter(final LogEvent event) { + Objects.requireNonNull(event, "The 'event' argument must not be null."); return filter(getMessageTextByType(event.getMessage())); } /** - * Apply the filter to the given message and return the match/mismatch result. + * Apply the filter to the given message and return the {@code onMatch} result if the entire + * message matches the configured regex pattern; otherwise, {@code onMismatch}. *

- * If the given '{@code msg}' is {@code null} the configured mismatch result will be returned. + * If the given '{@code msg}' is {@code null} the configured {@code onMismatch} result will be returned. *

* @param msg the message - * @return the filter result + * @return the {@code onMatch} result if the pattern matches; otherwise, the {@code onMismatch} result */ - public Result filter(final String msg) { - if (msg == null) { - return onMismatch; - } - return pattern.matcher(msg).matches() ? onMatch : onMismatch; + public Result filter(final @Nullable String msg) { + return (msg != null && pattern.matcher(msg).matches()) ? onMatch : onMismatch; } /** @@ -176,6 +242,7 @@ private String getMessageTextByType(final Message message) { : message.getFormattedMessage(); } + /** {@inheritDoc} */ @Override public String toString() { return "useRawMessage=" + useRawMessage + ", pattern=" + pattern.toString(); @@ -183,12 +250,11 @@ public String toString() { /** * Creates a new builder instance. - * * @return the new builder instance */ @PluginBuilderFactory - public static RegexFilter.Builder newBuilder() { - return new RegexFilter.Builder(); + public static Builder newBuilder() { + return new Builder(); } /** @@ -203,7 +269,8 @@ public static final class Builder extends AbstractFilterBuilder - * If the regular-expression is not set, or cannot be compiled to a valid pattern the validation will fail. - *

- * @return {@code true} if the regular-expression is valid; otherwise, {@code false} - */ - private boolean isRegexValid() { - if (regex == null) { - return false; - } - try { - Pattern.compile(regex); - } catch (final PatternSyntaxException ex) { - return false; - } - return true; - } } /* @@ -320,11 +357,11 @@ private boolean isRegexValid() { @Deprecated @SuppressWarnings("MagicConstant") private RegexFilter( - final boolean useRawMessage, final String regex, - final String[] patternFlags, - final Result onMatch, - final Result onMismatch) { + final boolean useRawMessage, + final @Nullable String @Nullable [] patternFlags, + final @Nullable Result onMatch, + final @Nullable Result onMismatch) { super(onMatch, onMismatch); Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter"); this.patternFlags = patternFlags == null ? new String[0] : patternFlags.clone(); @@ -368,32 +405,22 @@ public String[] getPatternFlags() { public static RegexFilter createFilter( // @formatter:off @PluginAttribute("regex") final String regex, - @PluginElement("PatternFlags") final String[] patternFlags, - @PluginAttribute("useRawMsg") final Boolean useRawMsg, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) + @PluginElement("PatternFlags") final String @Nullable [] patternFlags, + @PluginAttribute("useRawMsg") final @Nullable Boolean useRawMsg, + @PluginAttribute("onMatch") final @Nullable Result match, + @PluginAttribute("onMismatch") final @Nullable Result mismatch) // @formatter:on throws IllegalArgumentException, IllegalAccessException { // LOG4J-3086 - pattern-flags can be embedded in RegEx expression + Objects.requireNonNull(regex, "The 'regex' argument must not be null."); - boolean raw = Boolean.TRUE.equals(useRawMsg); - if (regex == null) { - LOGGER.error("A regular expression must be provided for RegexFilter"); - return null; - } - - try { - return new RegexFilter(raw, regex, patternFlags, match, mismatch); - } catch (final Exception ex) { - LOGGER.error("Unable to create RegexFilter. {}", ex.getMessage(), ex); - return null; - } + return new RegexFilter(regex, Boolean.TRUE.equals(useRawMsg), patternFlags, match, mismatch); } /** @deprecated pattern flags have been deprecated - they can just be included in the regex-expression. */ @Deprecated - private static int toPatternFlags(final String[] patternFlags) + private static int toPatternFlags(final String @Nullable [] patternFlags) throws IllegalArgumentException, IllegalAccessException { if (patternFlags == null || patternFlags.length == 0) { return DEFAULT_PATTERN_FLAGS; From 6f1358c605ac97b4e07aa72f18f48df76c19035c Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Sun, 2 Mar 2025 19:49:09 +0100 Subject: [PATCH 40/42] Fixed a reverse logic check in a validation (#3086) --- .../log4j/core/config/CompositeConfigurationTest.java | 9 ++++----- .../apache/logging/log4j/core/filter/RegexFilter.java | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java index 69956c1c845..fffd7bfda14 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java @@ -293,11 +293,10 @@ public void evaluate() throws Throwable { private void runTest(final LoggerContextRule rule, final Statement statement) { try { rule.apply( - statement, - Description.createTestDescription( - getClass(), - Thread.currentThread().getStackTrace()[1].getMethodName())) - .evaluate(); + statement, + Description.createTestDescription(getClass(), + Thread.currentThread().getStackTrace()[1].getMethodName())) + .evaluate(); } catch (final Throwable e) { Throwables.rethrow(e); } 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 ffe470af485..d97f70ea979 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 @@ -66,7 +66,7 @@ private RegexFilter(final Builder builder) { super(builder); - if (Strings.isNotBlank(builder.regex)) { + if (Strings.isBlank(builder.regex)) { throw new IllegalArgumentException("The 'regex' attribute must not be null or empty."); } @@ -317,8 +317,8 @@ public Builder setUseRawMsg(final boolean useRawMsg) { public @Nullable RegexFilter build() { // validate the "regex" attribute - if (this.regex == null) { - LOGGER.error("Unable to create RegexFilter: The 'regex' attribute must be provided."); + if (Strings.isEmpty(this.regex)) { + LOGGER.error("Unable to create RegexFilter: The 'regex' attribute be set to a non-empty String."); return null; } From c7f89d8c56c226909a64bbb38b9a4ef0030795be Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Sun, 2 Mar 2025 19:58:38 +0100 Subject: [PATCH 41/42] Fix Spotless errors (#3086) --- .../log4j/core/config/CompositeConfigurationTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java index fffd7bfda14..69956c1c845 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java @@ -293,10 +293,11 @@ public void evaluate() throws Throwable { private void runTest(final LoggerContextRule rule, final Statement statement) { try { rule.apply( - statement, - Description.createTestDescription(getClass(), - Thread.currentThread().getStackTrace()[1].getMethodName())) - .evaluate(); + statement, + Description.createTestDescription( + getClass(), + Thread.currentThread().getStackTrace()[1].getMethodName())) + .evaluate(); } catch (final Throwable e) { Throwables.rethrow(e); } From d6b55746928cfe8c19edb01ea26e726a6ba5a03d Mon Sep 17 00:00:00 2001 From: Jeff Thomas Date: Sun, 2 Mar 2025 20:21:55 +0100 Subject: [PATCH 42/42] Fix RegexFilterTest (#3086) --- .../apache/logging/log4j/core/filter/RegexFilterTest.java | 8 +------- .../org/apache/logging/log4j/core/filter/RegexFilter.java | 6 ++++++ 2 files changed, 7 insertions(+), 7 deletions(-) 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 b7a128b5bfb..3bf96c607fd 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 @@ -40,7 +40,7 @@ class RegexFilterTest { @BeforeAll static void before() { - StatusLogger.getLogger().setLevel(Level.OFF); + StatusLogger.getLogger().getFallbackListener().setLevel(Level.OFF); } @Test @@ -152,8 +152,6 @@ void testWithValidRegex() { .setOnMatch(Result.ACCEPT) .setOnMismatch(Result.DENY); - assertTrue(builder.isValid()); - final RegexFilter filter = builder.build(); assertNotNull(filter); @@ -195,8 +193,6 @@ void testBuilderWithoutRegexNotValid() { final RegexFilter.Builder builder = RegexFilter.newBuilder(); - assertFalse(builder.isValid()); - assertNull(builder.build()); } @@ -210,8 +206,6 @@ void testBuilderWithInvalidRegexNotValid() { builder.setRegex("[a-z"); - assertFalse(builder.isValid()); - assertNull(builder.build()); } } 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 d97f70ea979..839089b35bb 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 @@ -308,6 +308,12 @@ public Builder setUseRawMsg(final boolean useRawMsg) { return this; } + /** {@inheritDoc} */ + @Override + public boolean isValid() { + return (Strings.isNotEmpty(this.regex)); + } + /** * Builds and returns a {@link RegexFilter} instance configured by this builder. *