This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch ScopedContext-replace-with-interface in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit e972b5c72341ca2abf1f13d4954335ceef06fa09 Author: Ralph Goers <[email protected]> AuthorDate: Thu Apr 4 09:00:22 2024 -0700 Revert unit test change. Correct site changes --- .../log4j/message/ParameterizedMapMessageTest.java | 77 ------ .../log4j/message/ParameterizedMessageTest.java | 303 +++++++++++++++++++++ src/site/asciidoc/manual/extending.adoc | 10 +- 3 files changed, 308 insertions(+), 82 deletions(-) diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMapMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMapMessageTest.java deleted file mode 100644 index a560570846..0000000000 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMapMessageTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.message; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.apache.logging.log4j.test.ListStatusListener; -import org.apache.logging.log4j.test.junit.UsingStatusListener; -import org.junit.jupiter.api.Test; - -@UsingStatusListener -class ParameterizedMapMessageTest { - - final ListStatusListener statusListener; - - ParameterizedMapMessageTest(ListStatusListener statusListener) { - this.statusListener = statusListener; - } - - @Test - void testNoArgs() { - final String testMsg = "Test message {}"; - ParameterizedMessage msg = new ParameterizedMessage(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - assertThat(result).isEqualTo(testMsg); - final Object[] array = null; - msg = new ParameterizedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertThat(result).isEqualTo(testMsg); - } - - @Test - void testZeroLength() { - final String testMsg = ""; - ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"}); - String result = msg.getFormattedMessage(); - assertThat(result).isEqualTo(testMsg); - final Object[] array = null; - msg = new ParameterizedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertThat(result).isEqualTo(testMsg); - } - - @Test - void testOneCharLength() { - final String testMsg = "d"; - ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"}); - String result = msg.getFormattedMessage(); - assertThat(result).isEqualTo(testMsg); - final Object[] array = null; - msg = new ParameterizedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertThat(result).isEqualTo(testMsg); - } - - @Test - void testFormat3StringArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = {"a", "b", "c"}; - final String result = ParameterizedMessage.format(testMsg, args); - assertThat(result).isEqualTo("Test message ab c"); - } -} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java new file mode 100644 index 0000000000..4bd5df91be --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java @@ -0,0 +1,303 @@ +/* + * 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.message; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.Mutable; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +@UsingStatusListener +class ParameterizedMessageTest { + + final ListStatusListener statusListener; + + ParameterizedMessageTest(ListStatusListener statusListener) { + this.statusListener = statusListener; + } + + @Test + void testNoArgs() { + final String testMsg = "Test message {}"; + ParameterizedMessage msg = new ParameterizedMessage(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + final Object[] array = null; + msg = new ParameterizedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + } + + @Test + void testZeroLength() { + final String testMsg = ""; + ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"}); + String result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + final Object[] array = null; + msg = new ParameterizedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + } + + @Test + void testOneCharLength() { + final String testMsg = "d"; + ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"}); + String result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + final Object[] array = null; + msg = new ParameterizedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + } + + @Test + void testFormat3StringArgs() { + final String testMsg = "Test message {}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c"); + } + + @Test + void testFormatNullArgs() { + final String testMsg = "Test message {} {} {} {} {} {}"; + final String[] args = {"a", null, "c", null, null, null}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message a null c null null null"); + } + + @Test + void testFormatStringArgsIgnoresSuperfluousArgs() { + final String testMsg = "Test message {}{} {}"; + final String[] args = {"a", "b", "c", "unnecessary", "superfluous"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c"); + } + + @Test + void testFormatStringArgsWithEscape() { + final String testMsg = "Test message \\{}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message {}a b"); + } + + @Test + void testFormatStringArgsWithTrailingEscape() { + final String testMsg = "Test message {}{} {}\\"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c\\"); + } + + @Test + void testFormatStringArgsWithTrailingText() { + final String testMsg = "Test message {}{} {}Text"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab cText"); + } + + @Test + void testFormatStringArgsWithTrailingEscapedEscape() { + final String testMsg = "Test message {}{} {}\\\\"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c\\"); + } + + @Test + void testFormatStringArgsWithEscapedEscape() { + final String testMsg = "Test message \\\\{}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message \\ab c"); + } + + @Test + void testSafeWithMutableParams() { // LOG4J2-763 + final String testMsg = "Test message {}"; + final Mutable param = new Mutable().set("abc"); + final ParameterizedMessage msg = new ParameterizedMessage(testMsg, param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertThat(actual).isEqualTo("Test message XYZ").as("Should use current param value"); + + // modify parameter after calling msg.getFormattedMessage + param.set("000"); + final String after = msg.getFormattedMessage(); + assertThat(after).isEqualTo("Test message XYZ").as("Should not change after rendered once"); + } + + static Stream<Object> testSerializable() { + @SuppressWarnings("EqualsHashCode") + class NonSerializable { + @Override + public boolean equals(final Object other) { + return other instanceof NonSerializable; // a very lenient equals() + } + } + return Stream.of( + "World", + new NonSerializable(), + new BigDecimal("123.456"), + // LOG4J2-3680 + new RuntimeException(), + null); + } + + @ParameterizedTest + @MethodSource + void testSerializable(final Object arg) { + final Message expected = new ParameterizedMessage("Hello {}!", arg); + final Message actual = SerialUtil.deserialize(SerialUtil.serialize(expected)); + assertThat(actual).isInstanceOf(ParameterizedMessage.class); + assertThat(actual.getFormattedMessage()).isEqualTo(expected.getFormattedMessage()); + } + + /** + * In this test cases, constructed the following scenarios: <br> + * <p> + * 1. The arguments contains an exception, and the count of placeholder is equal to arguments include exception. <br> + * 2. The arguments contains an exception, and the count of placeholder is equal to arguments except exception.<br> + * All of these should not logged in status logger. + * </p> + * + * @return Streams + */ + static Stream<Object[]> testCasesWithExceptionArgsButNoWarn() { + return Stream.of( + new Object[] { + "with exception {} {}", + new Object[] {"a", new RuntimeException()}, + "with exception a java.lang.RuntimeException" + }, + new Object[] { + "with exception {} {}", new Object[] {"a", "b", new RuntimeException()}, "with exception a b" + }); + } + + @ParameterizedTest + @MethodSource("testCasesWithExceptionArgsButNoWarn") + void formatToWithExceptionButNoWarn(final String pattern, final Object[] args, final String expected) { + final ParameterizedMessage message = new ParameterizedMessage(pattern, args); + final StringBuilder buffer = new StringBuilder(); + message.formatTo(buffer); + assertThat(buffer.toString()).isEqualTo(expected); + final List<StatusData> statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).hasSize(0); + } + + @ParameterizedTest + @MethodSource("testCasesWithExceptionArgsButNoWarn") + void formatWithExceptionButNoWarn(final String pattern, final Object[] args, final String expected) { + final String message = ParameterizedMessage.format(pattern, args); + assertThat(message).isEqualTo(expected); + final List<StatusData> statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).hasSize(0); + } + + /** + * In this test cases, constructed the following scenarios: <br> + * <p> + * 1. The placeholders are greater than the count of arguments. <br> + * 2. The placeholders are less than the count of arguments. <br> + * 3. The arguments contains an exception, and the placeholder is greater than normal arguments. <br> + * 4. The arguments contains an exception, and the placeholder is less than the arguments.<br> + * All of these should logged in status logger with WARN level. + * </p> + * + * @return streams + */ + static Stream<Object[]> testCasesForInsufficientFormatArgs() { + return Stream.of( + new Object[] {"more {} {}", 2, new Object[] {"a"}, "more a {}"}, + new Object[] {"more {} {} {}", 3, new Object[] {"a"}, "more a {} {}"}, + new Object[] {"less {}", 1, new Object[] {"a", "b"}, "less a"}, + new Object[] {"less {} {}", 2, new Object[] {"a", "b", "c"}, "less a b"}, + new Object[] { + "more throwable {} {} {}", + 3, + new Object[] {"a", new RuntimeException()}, + "more throwable a java.lang.RuntimeException {}" + }, + new Object[] { + "less throwable {}", 1, new Object[] {"a", "b", new RuntimeException()}, "less throwable a" + }); + } + + @ParameterizedTest + @MethodSource("testCasesForInsufficientFormatArgs") + void formatToShouldWarnOnInsufficientArgs( + final String pattern, final int placeholderCount, final Object[] args, final String expected) { + final int argCount = args == null ? 0 : args.length; + verifyFormattingFailureOnInsufficientArgs(pattern, placeholderCount, argCount, expected, () -> { + final ParameterizedMessage message = new ParameterizedMessage(pattern, args); + final StringBuilder buffer = new StringBuilder(); + message.formatTo(buffer); + return buffer.toString(); + }); + } + + @ParameterizedTest + @MethodSource("testCasesForInsufficientFormatArgs") + void formatShouldWarnOnInsufficientArgs( + final String pattern, final int placeholderCount, final Object[] args, final String expected) { + final int argCount = args == null ? 0 : args.length; + verifyFormattingFailureOnInsufficientArgs( + pattern, placeholderCount, argCount, expected, () -> ParameterizedMessage.format(pattern, args)); + } + + private void verifyFormattingFailureOnInsufficientArgs( + final String pattern, + final int placeholderCount, + final int argCount, + final String expected, + final Supplier<String> formattedMessageSupplier) { + + // Verify the formatted message + final String formattedMessage = formattedMessageSupplier.get(); + assertThat(formattedMessage).isEqualTo(expected); + + // Verify the status logger warn + final List<StatusData> statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).hasSize(1); + final StatusData statusData = statusDataList.get(0); + assertThat(statusData.getLevel()).isEqualTo(Level.WARN); + assertThat(statusData.getMessage().getFormattedMessage()) + .isEqualTo( + "found %d argument placeholders, but provided %d for pattern `%s`", + placeholderCount, argCount, pattern); + assertThat(statusData.getThrowable()).isNull(); + } +} diff --git a/src/site/asciidoc/manual/extending.adoc b/src/site/asciidoc/manual/extending.adoc index 03e84c26e7..53dd637a7e 100644 --- a/src/site/asciidoc/manual/extending.adoc +++ b/src/site/asciidoc/manual/extending.adoc @@ -587,14 +587,14 @@ ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLi [#Custom_ContextDataProvider] == Custom ContextDataProvider -The link:../log4j-api/apidocs/org/apache/logging/log4j/spi/ContextDataProvider.html[`ContextDataProvider`] -(introduced in Log4j 2.13.2 and moved to log4j-api in 2.24.0) is an interface applications and libraries +The link:../log4j-core/apidocs/org/apache/logging/log4j/core/util/ContextDataProvider.html[`ContextDataProvider`] +(introduced in Log4j 2.13.2) is an interface applications and libraries can use to inject additional key-value pairs into the LogEvent's context data. Log4j itself adds the ThreadContext data to the LogEvent using -`org.apache.logging.log4j.spi.ThreadContextDataProvider`. Custom implementations -should implement the `org.apache.logging.log4j.spi.ContextDataProvider` interface and +`org.apache.logging.log4j.core.impl.ThreadContextDataProvider`. Custom implementations +should implement the `org.apache.logging.log4j.core.util.ContextDataProvider` interface and declare it as a service by defining the implmentation class in a file named -`META-INF/services/org.apache.logging.log4j.spi.ContextDataProvider`. +`META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider`. == Custom ThreadContextMap implementations
