Commit 3df7f50e authored by Ralph Goers's avatar Ralph Goers
Browse files

LOG4J2-2008 - support multiple structured data elements

parent 4992db7b
Showing with 226 additions and 16 deletions
+226 -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.
*/
package org.apache.logging.log4j.message;
/**
* A Message that is a collection of Messages.
* @param <T> The Message type.
*/
public interface MessageCollectionMessage<T> extends Message, Iterable<T> {
}
/*
* 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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* A collection of StructuredDataMessages.
*/
public class StructuredDataCollectionMessage implements MessageCollectionMessage<StructuredDataMessage> {
private List<StructuredDataMessage> structuredDataMessageList;
public StructuredDataCollectionMessage(List<StructuredDataMessage> messages) {
this.structuredDataMessageList = messages;
}
@Override
public Iterator<StructuredDataMessage> iterator() {
return structuredDataMessageList.iterator();
}
@Override
public String getFormattedMessage() {
StringBuilder sb = new StringBuilder();
for (StructuredDataMessage msg : structuredDataMessageList) {
sb.append(msg.getFormattedMessage());
}
return sb.toString();
}
@Override
public String getFormat() {
StringBuilder sb = new StringBuilder();
for (StructuredDataMessage msg : structuredDataMessageList) {
if (msg.getFormat() != null) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(msg.getFormat());
}
}
return sb.toString();
}
@Override
public Object[] getParameters() {
List<Object[]> objectList = new ArrayList<>();
int count = 0;
for (StructuredDataMessage msg : structuredDataMessageList) {
Object[] objects = msg.getParameters();
if (objects != null) {
objectList.add(objects);
count += objects.length;
}
}
Object[] objects = new Object[count];
int index = 0;
for (Object[] objs : objectList) {
for (Object obj : objs) {
objects[index++] = obj;
}
}
return objects;
}
@Override
public Throwable getThrowable() {
for (StructuredDataMessage msg : structuredDataMessageList) {
Throwable t = msg.getThrowable();
if (t != null) {
return t;
}
}
return null;
}
}
package org.apache.logging.log4j.util;
public class ProcessIdUtil {
public static String getProcessId() {
return "-";
}
}
......@@ -51,8 +51,11 @@ import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.core.util.Patterns;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageCollectionMessage;
import org.apache.logging.log4j.message.StructuredDataCollectionMessage;
import org.apache.logging.log4j.message.StructuredDataId;
import org.apache.logging.log4j.message.StructuredDataMessage;
import org.apache.logging.log4j.util.ProcessIdUtil;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.logging.log4j.util.Strings;
......@@ -190,8 +193,7 @@ public final class Rfc5424Layout extends AbstractStringLayout {
final String name = config == null ? null : config.getName();
configName = Strings.isNotEmpty(name) ? name : null;
this.fieldFormatters = createFieldFormatters(loggerFields, config);
// TODO Java 9: ProcessHandle.current().getPid();
this.procId = "-";
this.procId = ProcessIdUtil.getProcessId();
}
private Map<String, FieldFormatter> createFieldFormatters(final LoggerFields[] loggerFields,
......@@ -331,8 +333,8 @@ public final class Rfc5424Layout extends AbstractStringLayout {
private void appendMessage(final StringBuilder buffer, final LogEvent event) {
final Message message = event.getMessage();
// This layout formats StructuredDataMessages instead of delegating to the Message itself.
final String text = (message instanceof StructuredDataMessage) ? message.getFormat() : message
.getFormattedMessage();
final String text = (message instanceof StructuredDataMessage || message instanceof MessageCollectionMessage)
? message.getFormat() : message.getFormattedMessage();
if (text != null && text.length() > 0) {
buffer.append(' ').append(escapeNewlines(text, escapeNewLine));
......@@ -352,7 +354,8 @@ public final class Rfc5424Layout extends AbstractStringLayout {
private void appendStructuredElements(final StringBuilder buffer, final LogEvent event) {
final Message message = event.getMessage();
final boolean isStructured = message instanceof StructuredDataMessage;
final boolean isStructured = message instanceof StructuredDataMessage ||
message instanceof StructuredDataCollectionMessage;
if (!isStructured && (fieldFormatters != null && fieldFormatters.isEmpty()) && !includeMdc) {
buffer.append('-');
......@@ -387,18 +390,12 @@ public final class Rfc5424Layout extends AbstractStringLayout {
}
if (isStructured) {
final StructuredDataMessage data = (StructuredDataMessage) message;
final Map<String, String> map = data.getData();
final StructuredDataId id = data.getId();
final String sdId = getId(id);
if (sdElements.containsKey(sdId)) {
final StructuredDataElement union = sdElements.get(id.toString());
union.union(map);
sdElements.put(sdId, union);
if (message instanceof MessageCollectionMessage) {
for (StructuredDataMessage data : ((StructuredDataCollectionMessage)message)) {
addStructuredData(sdElements, data);
}
} else {
final StructuredDataElement formattedData = new StructuredDataElement(map, eventPrefix, false);
sdElements.put(sdId, formattedData);
addStructuredData(sdElements, (StructuredDataMessage) message);
}
}
......@@ -412,6 +409,21 @@ public final class Rfc5424Layout extends AbstractStringLayout {
}
}
private void addStructuredData(final Map<String, StructuredDataElement> sdElements, final StructuredDataMessage data) {
final Map<String, String> map = data.getData();
final StructuredDataId id = data.getId();
final String sdId = getId(id);
if (sdElements.containsKey(sdId)) {
final StructuredDataElement union = sdElements.get(id.toString());
union.union(map);
sdElements.put(sdId, union);
} else {
final StructuredDataElement formattedData = new StructuredDataElement(map, eventPrefix, false);
sdElements.put(sdId, formattedData);
}
}
private String escapeNewlines(final String text, final String replacement) {
if (null == replacement) {
return text;
......
......@@ -16,6 +16,7 @@
*/
package org.apache.logging.log4j.core.layout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
......@@ -31,6 +32,7 @@ import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.net.Facility;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.junit.ThreadContextRule;
import org.apache.logging.log4j.message.StructuredDataCollectionMessage;
import org.apache.logging.log4j.message.StructuredDataMessage;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.test.appender.ListAppender;
......@@ -58,6 +60,9 @@ public class Rfc5424LayoutTest {
private static final String lineEscaped4 =
"ATM - Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"]" +
"[RequestContext@3692 escaped=\"Testing escaping #012 \\\" \\] \\\"\" ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete";
private static final String collectionLine = "[Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" " +
"ToAccount=\"123456\"][Extra@18060 Item1=\"Hello\" Item2=\"World\"][RequestContext@3692 " +
"ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete";
static ConfigurationFactory cf = new BasicConfigurationFactory();
......@@ -148,6 +153,72 @@ public class Rfc5424LayoutTest {
list = appender.getMessages();
assertTrue("No messages expected, found " + list.size(), list.isEmpty());
} finally {
ThreadContext.clearMap();
root.removeAppender(appender);
appender.stop();
}
}
/**
* Test case for MDC conversion pattern.
*/
@Test
public void testCollection() throws Exception {
for (final Appender appender : root.getAppenders().values()) {
root.removeAppender(appender);
}
// set up appender
final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext",
null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null);
final ListAppender appender = new ListAppender("List", null, layout, true, false);
appender.start();
// set appender on root and set level to debug
root.addAppender(appender);
root.setLevel(Level.DEBUG);
ThreadContext.put("loginId", "JohnDoe");
ThreadContext.put("ipAddress", "192.168.0.120");
ThreadContext.put("locale", Locale.US.getDisplayName());
try {
final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit");
msg.put("ToAccount", "123456");
msg.put("FromAccount", "123457");
msg.put("Amount", "200.00");
final StructuredDataMessage msg2 = new StructuredDataMessage("Extra@18060", null, "Audit");
msg2.put("Item1", "Hello");
msg2.put("Item2", "World");
List<StructuredDataMessage> messages = new ArrayList<>();
messages.add(msg);
messages.add(msg2);
final StructuredDataCollectionMessage collectionMessage = new StructuredDataCollectionMessage(messages);
root.info(MarkerManager.getMarker("EVENT"), collectionMessage);
List<String> list = appender.getMessages();
assertTrue("Expected line 1 to end with: " + collectionLine + " Actual " + list.get(0),
list.get(0).endsWith(collectionLine));
for (final String frame : list) {
int length = -1;
final int frameLength = frame.length();
final int firstSpacePosition = frame.indexOf(' ');
final String messageLength = frame.substring(0, firstSpacePosition);
try {
length = Integer.parseInt(messageLength);
// the ListAppender removes the ending newline, so we expect one less size
assertEquals(frameLength, messageLength.length() + length);
}
catch (final NumberFormatException e) {
assertTrue("Not a valid RFC 5425 frame", false);
}
}
appender.clear();
} finally {
ThreadContext.clearMap();
root.removeAppender(appender);
appender.stop();
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment