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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions src/main/java/net/logstash/log4j/JSONEventLayoutV0.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

public class JSONEventLayoutV0 extends Layout {

private boolean renderObjectFields = false;
private boolean locationInfo = false;

private String tags;
Expand Down Expand Up @@ -44,16 +49,19 @@ public static String dateFormat(long timestamp) {
* in the log messages.
*/
public JSONEventLayoutV0() {
this(true);
this(true, false);
}

/**
* Creates a layout that optionally inserts location information into log messages.
*
* @param locationInfo whether or not to include location information in the log messages.
* @param renderObjectFields whether or not to render the fields of the message object into the json when an object is logged to log4j.
* Rendering the fields is done using reflection and incurs a performance cost
*/
public JSONEventLayoutV0(boolean locationInfo) {
public JSONEventLayoutV0(boolean locationInfo, boolean renderObjectFields) {
this.locationInfo = locationInfo;
this.renderObjectFields = renderObjectFields;
}

public String format(LoggingEvent loggingEvent) {
Expand All @@ -70,6 +78,13 @@ public String format(LoggingEvent loggingEvent) {
logstashEvent.put("@message", loggingEvent.getRenderedMessage());
logstashEvent.put("@timestamp", dateFormat(timestamp));

if (renderObjectFields) {
Object messageObj = loggingEvent.getMessage();
if (messageObj instanceof Serializable && !(messageObj instanceof String)) {
addObjectFieldData(messageObj);
}
}

if (loggingEvent.getThrowableInformation() != null) {
final ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation();
if (throwableInformation.getThrowable().getClass().getCanonicalName() != null) {
Expand Down Expand Up @@ -103,6 +118,32 @@ public String format(LoggingEvent loggingEvent) {
return logstashEvent.toString() + "\n";
}

private void addObjectFieldData(Object messageObj) {
Field[] fields = messageObj.getClass().getDeclaredFields();
Object value;

for(Field f : fields) {
try {
value = f.get(messageObj);
if (value != null) fieldData.put(f.getName(), value);
} catch (IllegalAccessException e) {
}
}
Method[] methods = messageObj.getClass().getDeclaredMethods();
for(Method m : methods)
{
if(m.getName().startsWith("get"))
{
try {
value = m.invoke(messageObj);
if (value != null) fieldData.put(m.getName().substring(3), value);
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
}
}
}

public boolean ignoresThrowable() {
return ignoreThrowable;
}
Expand All @@ -125,6 +166,19 @@ public void setLocationInfo(boolean locationInfo) {
this.locationInfo = locationInfo;
}

/**
* Set whether or not to render the fields of the message object into the json when an object is logged to log4j.
* Rendering the fields is done using reflection and incurs a performance cost
* @param renderObjectFields
*/
public void setRenderObjectFields(boolean renderObjectFields) {
this.renderObjectFields = renderObjectFields;
}

public boolean getRenderObjectFields() {
return renderObjectFields;
}

public void activateOptions() {
activeIgnoreThrowable = ignoreThrowable;
}
Expand Down
56 changes: 54 additions & 2 deletions src/main/java/net/logstash/log4j/JSONEventLayoutV1.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

public class JSONEventLayoutV1 extends Layout {

private boolean renderObjectFields = false;
private boolean locationInfo = false;
private String customUserFields;

Expand Down Expand Up @@ -47,15 +52,19 @@ public static String dateFormat(long timestamp) {
* in the log messages.
*/
public JSONEventLayoutV1() {
this(true);
this(true, false);
}

/**
* Creates a layout that optionally inserts location information into log messages.
*
* @param locationInfo whether or not to include location information in the log messages.
* @param renderObjectFields whether or not to render the fields of the message object into the json when an object is logged to log4j.
* Rendering the fields is done using reflection and incurs a performance cost

*/
public JSONEventLayoutV1(boolean locationInfo) {
public JSONEventLayoutV1(boolean locationInfo, boolean renderObjectFields) {
this.renderObjectFields = renderObjectFields;
this.locationInfo = locationInfo;
}

Expand Down Expand Up @@ -105,6 +114,13 @@ public String format(LoggingEvent loggingEvent) {
logstashEvent.put("source_host", hostname);
logstashEvent.put("message", loggingEvent.getRenderedMessage());

if (renderObjectFields) {
Object messageObject = loggingEvent.getMessage();
if (messageObject instanceof Serializable && ! (messageObject instanceof String)) {
addObjectFieldData(messageObject);
}
}

if (loggingEvent.getThrowableInformation() != null) {
final ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation();
if (throwableInformation.getThrowable().getClass().getCanonicalName() != null) {
Expand Down Expand Up @@ -159,6 +175,19 @@ public void setLocationInfo(boolean locationInfo) {
this.locationInfo = locationInfo;
}

/**
* Set whether or not to render the fields of the message object into the json when an object is logged to log4j.
* Rendering the fields is done using reflection and incurs a performance cost
* @param renderObjectFields
*/
public void setRenderObjectFields(boolean renderObjectFields) {
this.renderObjectFields = renderObjectFields;
}

public boolean getRenderObjectFields() {
return renderObjectFields;
}

public String getUserFields() { return customUserFields; }
public void setUserFields(String userFields) { this.customUserFields = userFields; }

Expand All @@ -184,4 +213,27 @@ private void addEventData(String keyname, Object keyval) {
logstashEvent.put(keyname, keyval);
}
}

private void addObjectFieldData(Object messageObj) {
Field[] fields = messageObj.getClass().getDeclaredFields();

for(Field f : fields) {
try {
addEventData(f.getName(), f.get(messageObj));
} catch (IllegalAccessException e) {
}
}
Method[] methods = messageObj.getClass().getDeclaredMethods();
for(Method m : methods)
{
if(m.getName().startsWith("get"))
{
try {
addEventData(m.getName().substring(3), m.invoke(messageObj));
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
}
}
}
}
21 changes: 21 additions & 0 deletions src/test/java/net/logstash/log4j/JSONEventLayoutV0Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.junit.Ignore;
import org.junit.Test;

import java.io.Serializable;

/**
* Created with IntelliJ IDEA.
* User: jvincent
Expand Down Expand Up @@ -158,6 +160,25 @@ public void testJSONEventHasThreadName() {
Assert.assertNotNull("ThreadName value is missing", atFields.get("threadName"));
}

@Test
public void testMessageObjectRendering() {
JSONEventLayoutV0 layout = (JSONEventLayoutV0) appender.getLayout();
boolean prevRenderObjectFields = layout.getRenderObjectFields();
layout.setRenderObjectFields(true);
logger.info(new Serializable() {
String test = "TEST";
int testNum = 1123;
});
String message = appender.getMessages()[0];
Object obj = JSONValue.parse(message);
JSONObject jsonObject = (JSONObject) obj;
JSONObject atFields = (JSONObject) jsonObject.get("@fields");
Assert.assertTrue(atFields.containsKey("testNum"));
Assert.assertTrue(atFields.get("testNum") instanceof Integer);
Assert.assertEquals(atFields.get("testNum"), 1123);
layout.setRenderObjectFields(prevRenderObjectFields);
}

@Test
public void testJSONEventLayoutNoLocationInfo() {
JSONEventLayoutV0 layout = (JSONEventLayoutV0) appender.getLayout();
Expand Down
37 changes: 28 additions & 9 deletions src/test/java/net/logstash/log4j/JSONEventLayoutV1Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
import junit.framework.Assert;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONValue;
import org.apache.log4j.*;
import org.apache.log4j.or.ObjectRenderer;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import org.apache.log4j.NDC;
import org.junit.After;
import org.junit.Before;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import java.io.Serializable;
import java.util.HashMap;

/**
Expand Down Expand Up @@ -69,7 +70,7 @@ public void testJSONEventLayoutHasUserFieldsFromProps() {
Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message));
Object obj = JSONValue.parse(message);
JSONObject jsonObject = (JSONObject) obj;
Assert.assertTrue("Event does not contain field 'field1'" , jsonObject.containsKey("field1"));
Assert.assertTrue("Event does not contain field 'field1'", jsonObject.containsKey("field1"));
Assert.assertEquals("Event does not contain value 'value1'", "propval1", jsonObject.get("field1"));
System.clearProperty(JSONEventLayoutV1.ADDITIONAL_DATA_PROPERTY);
}
Expand Down Expand Up @@ -125,7 +126,7 @@ public void testJSONEventLayoutUserFieldsPropOverride() {
Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message));
Object obj = JSONValue.parse(message);
JSONObject jsonObject = (JSONObject) obj;
Assert.assertTrue("Event does not contain field 'field1'" , jsonObject.containsKey("field1"));
Assert.assertTrue("Event does not contain field 'field1'", jsonObject.containsKey("field1"));
Assert.assertEquals("Event does not contain value 'propval1'", "propval1", jsonObject.get("field1"));

layout.setUserFields(prevUserData);
Expand All @@ -144,6 +145,24 @@ public void testJSONEventLayoutHasKeys() {
}
}

@Test
public void testMessageObjectRendering() {
JSONEventLayoutV1 layout = (JSONEventLayoutV1) appender.getLayout();
boolean prevRenderObjectFields = layout.getRenderObjectFields();
layout.setRenderObjectFields(true);
logger.info(new Serializable() {
String test = "TEST";
int testNum = 1123;
});
String message = appender.getMessages()[0];
Object obj = JSONValue.parse(message);
JSONObject jsonObject = (JSONObject) obj;
Assert.assertTrue(jsonObject.containsKey("testNum"));
Assert.assertTrue(jsonObject.get("testNum") instanceof Integer);
Assert.assertEquals(jsonObject.get("testNum"), 1123);
layout.setRenderObjectFields(prevRenderObjectFields);
}

@Test
public void testJSONEventLayoutHasNDC() {
String ndcData = new String("json-layout-test");
Expand All @@ -165,14 +184,14 @@ public void testJSONEventLayoutHasMDC() {
JSONObject jsonObject = (JSONObject) obj;
JSONObject mdc = (JSONObject) jsonObject.get("mdc");

Assert.assertEquals("MDC is wrong","bar", mdc.get("foo"));
Assert.assertEquals("MDC is wrong", "bar", mdc.get("foo"));
}

@Test
public void testJSONEventLayoutHasNestedMDC() {
HashMap nestedMdc = new HashMap<String, String>();
nestedMdc.put("bar","baz");
MDC.put("foo",nestedMdc);
nestedMdc.put("bar", "baz");
MDC.put("foo", nestedMdc);
logger.warn("I should have nested MDC data in my log");
String message = appender.getMessages()[0];
Object obj = JSONValue.parse(message);
Expand Down