diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java index 3d3f403bef7..4d1a1d416d6 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java @@ -64,6 +64,7 @@ void testMsgXml() { final String expected = "\n" + "Alert\n" + "MsgId@12345\n" + + "Test message {}\n" + "\n" + " This is a very long test memo to prevent regression of LOG4J2-114\n" + " Test message {}\n" @@ -73,12 +74,45 @@ void testMsgXml() { assertEquals(expected, result); } + @Test + void testMsgXmlIncludesConstructorMessage() { + // #4141: constructor message must appear (and be escaped) even when not put into the map + final StructuredDataMessage msg = new StructuredDataMessage("an id", "a & more", "a type"); + final String result = msg.getFormattedMessage(new String[] {"XML"}); + final String expected = "\n" + + "a type\n" + + "an id\n" + + "a <msg> & more\n" + + "\n" + + "\n" + + "\n"; + assertEquals(expected, result); + } + + @Test + void testMsgXmlDistinguishesConstructorMessageFromMapEntry() { + // #4141: free-form message and a map entry keyed "message" are independent + final StructuredDataMessage msg = new StructuredDataMessage("an id", "a message", "a type"); + msg.put("message", "foo"); + final String result = msg.getFormattedMessage(new String[] {"XML"}); + final String expected = "\n" + + "a type\n" + + "an id\n" + + "a message\n" + + "\n" + + " foo\n" + + "\n" + + "\n"; + assertEquals(expected, result); + } + @Test void testXmlEncodingOfIdAndType1() { final String id = "i<&d>" + XmlFixture.TEXT; final String type = "t>yp) final String actualXml = - new StructuredDataMessage(id, "discarded message", type).getFormattedMessage(new String[] {"XML"}); + new StructuredDataMessage(id, null, type).getFormattedMessage(new String[] {"XML"}); final String expectedXml = "\n" + "t>yp<e&" + XmlFixture.ENCODED_TEXT + "\n" @@ -101,7 +135,7 @@ void testXmlEncodingOfIdAndType2() { final StructuredDataId id = new StructuredDataId(idName, idEnterpriseNumber, idRequired, idOptional, Integer.MAX_VALUE); final String actualXml = - new StructuredDataMessage(id, "discarded message", type).getFormattedMessage(new String[] {"XML"}); + new StructuredDataMessage(id, null, type).getFormattedMessage(new String[] {"XML"}); final String expectedXml = "\n" + "t>yp<e&" + XmlFixture.ENCODED_TEXT + "\n" diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java index 69fa3fb8cbe..748c975c5ac 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java @@ -373,6 +373,15 @@ private void asXml(final StructuredDataId structuredDataId, final StringBuilder StringBuilders.escapeXml(sb, start); sb.append("\n"); + // Encode message as its own element (distinct from a map entry keyed "message") + if (message != null) { + sb.append(""); + start = sb.length(); + sb.append(message); + StringBuilders.escapeXml(sb, start); + sb.append("\n"); + } + // Encode the rest super.asXml(sb); diff --git a/src/changelog/.2.x.x/fix-StructuredDataMessage-XML-message.xml b/src/changelog/.2.x.x/fix-StructuredDataMessage-XML-message.xml new file mode 100644 index 00000000000..dbe0c90340d --- /dev/null +++ b/src/changelog/.2.x.x/fix-StructuredDataMessage-XML-message.xml @@ -0,0 +1,12 @@ + + + + + Include the free-form `message` field when encoding `StructuredDataMessage` to XML + +