Write log in Json format using log4j
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.AppenderAttachable;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
public class JsonLayout extends Layout {
private static final Pattern SEP_PATTERN = Pattern.compile("(?:\\p{Space}*?[,;]\\p{Space}*)+");
private static final Pattern PAIR_SEP_PATTERN = Pattern.compile("(?:\\p{Space}*?[:=]\\p{Space}*)+");
private static final char[] HEX_CHARS =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private class LoggerField {
private String defaultLabel;
private String renderedLabel;
private boolean isEnabled = true;
LoggerField(String defaultName) {
this.defaultLabel = defaultName;
this.renderedLabel = defaultName;
}
LoggerField(String defaultName, String renderedLabel) {
this.defaultLabel = defaultName;
this.renderedLabel = renderedLabel;
}
void enable() {
isEnabled = true;
}
}
private class FieldLabels {
private final ArrayList<LoggerField> allFields = new ArrayList<LoggerField>();
final LoggerField exceptionClass = loggerfield("exception.class", "class");
final LoggerField exceptionMessage = loggerfield("exception.message", "message");
final LoggerField exceptionStacktrace = loggerfield("exception.stacktrace", "stacktrace");
final LoggerField exception = loggerfield("exception");
final LoggerField level = loggerfield("level");
final LoggerField logger = loggerfield("logger");
final LoggerField message = loggerfield("message");
final LoggerField host = loggerfield("host");
final LoggerField path = loggerfield("path");
final LoggerField timestamp = loggerfield("timestamp");
final LoggerField thread = loggerfield("thread");
final LoggerField version = loggerfield("version");
private LoggerField loggerfield(String defaultName) {
return loggerfield(defaultName, null);
}
private LoggerField loggerfield(String defaultName, String renderedLabel) {
LoggerField loggerField;
if(renderedLabel != null) {
loggerField = new LoggerField(defaultName, renderedLabel);
} else {
loggerField = new LoggerField(defaultName);
}
allFields.add(loggerField);
return loggerField;
}
void enable(String fieldName) {
for (LoggerField field : allFields) {
if (field.defaultLabel.startsWith(fieldName)) {
field.enable();
}
}
}
}
private static final String VERSION = "1";
private String fieldsVal;
private String includedFields;
private final Map<String, String> fields;
private FieldLabels fieldLabels = new FieldLabels();
private final DateFormat dateFormat;
private final Date date;
private final StringBuilder buffer;
private String path;
private boolean pathResolved;
private String hostName;
private boolean ignoresThrowable;
public JsonLayout2() {
fields = new HashMap<String, String>();
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
date = new Date();
buffer = new StringBuilder(32*1024);
}
@Override
public String format(LoggingEvent event) {
buffer.setLength(0);
buffer.append('{');
boolean hasPrevField = false;
if (fieldLabels.exception.isEnabled) {
hasPrevField = appendException(buffer, event);
}
if (hasPrevField) {
buffer.append(',');
}
hasPrevField = appendFields(buffer, event);
if (fieldLabels.level.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.level.renderedLabel, event.getLevel().toString());
hasPrevField = true;
}
if (fieldLabels.logger.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.logger.renderedLabel, event.getLoggerName());
hasPrevField = true;
}
if (fieldLabels.message.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.message.renderedLabel, event.getRenderedMessage());
hasPrevField = true;
}
if (fieldLabels.host.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.host.renderedLabel, hostName);
hasPrevField = true;
}
if (fieldLabels.path.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
hasPrevField = appendSourcePath(buffer, event);
}
if (fieldLabels.timestamp.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
date.setTime(event.getTimeStamp());
appendField(buffer, fieldLabels.timestamp.renderedLabel, dateFormat.format(date));
hasPrevField = true;
}
if (fieldLabels.thread.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.thread.renderedLabel, event.getThreadName());
hasPrevField = true;
}
if (fieldLabels.version.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.version.renderedLabel, VERSION);
}
buffer.append("}\n");
return buffer.toString();
}
@SuppressWarnings("UnusedParameters")
private boolean appendFields(StringBuilder buf, LoggingEvent event) {
if (fields.isEmpty()) {
return false;
}
for (Iterator<Map.Entry<String, String>> iter = fields.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry<String, String> entry = iter.next();
appendField(buf, entry.getKey(), entry.getValue());
if (iter.hasNext()) {
buf.append(',');
}
}
return true;
}
private boolean appendSourcePath(StringBuilder buf, LoggingEvent event) {
if (!pathResolved) {
@SuppressWarnings("unchecked")
Appender appender = findLayoutAppender(event.getLogger());
if (appender instanceof FileAppender) {
FileAppender fileAppender = (FileAppender) appender;
path = getAppenderPath(fileAppender);
}
pathResolved = true;
}
if (path != null) {
appendField(buf, fieldLabels.path.renderedLabel, path);
return true;
}
return false;
}
private Appender findLayoutAppender(Category logger) {
for(Category parent = logger; parent != null; parent = parent.getParent()) {
@SuppressWarnings("unchecked")
Appender appender = findLayoutAppender(parent.getAllAppenders());
if(appender != null) {
return appender;
}
}
return null;
}
@SuppressWarnings("unchecked")
private Appender findLayoutAppender(Enumeration<? extends Appender> appenders) {
if(appenders == null) {
return null;
}
while (appenders.hasMoreElements()) {
Appender appender = appenders.nextElement();
// get the first appender with this layout instance and ignore others;
// actually a single instance of this class is not intended to be used with multiple threads.
if (appender.getLayout() == this) {
return appender;
}
if (appender instanceof AppenderAttachable) {
AppenderAttachable appenderContainer = (AppenderAttachable) appender;
return findLayoutAppender(appenderContainer.getAllAppenders());
}
}
return null;
}
private String getAppenderPath(FileAppender fileAppender) {
String path = null;
try {
String fileName = fileAppender.getFile();
if (fileName != null && !fileName.isEmpty()) {
path = new File(fileName).getCanonicalPath();
}
} catch (IOException e) {
LogLog.error("Unable to retrieve appender's file name", e);
}
return path;
}
private boolean appendException(StringBuilder buffer, LoggingEvent event) {
ThrowableInformation throwableInfo = event.getThrowableInformation();
if (throwableInfo == null) {
return false;
}
appendQuotedName(buffer, fieldLabels.exception.renderedLabel);
buffer.append(":{");
boolean hasPrevField = false;
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
Throwable throwable = throwableInfo.getThrowable();
if (throwable != null) {
String message = throwable.getMessage();
if (message != null) {
appendField(buffer, fieldLabels.exceptionMessage.renderedLabel, message);
hasPrevField = true;
}
String className = throwable.getClass().getCanonicalName();
if (className != null) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.exceptionClass.renderedLabel, className);
hasPrevField = true;
}
}
String[] stackTrace = throwableInfo.getThrowableStrRep();
if (stackTrace != null && stackTrace.length != 0) {
if (hasPrevField) {
buffer.append(',');
}
appendQuotedName(buffer, fieldLabels.exceptionStacktrace.renderedLabel);
buffer.append(":\"");
for (int i = 0, len = stackTrace.length; i < len; i++) {
appendValue(buffer, stackTrace[i]);
if (i != len - 1) {
appendChar(buffer, '\n');
}
}
buffer.append('\"');
}
buffer.append('}');
return true;
}
@Override
public boolean ignoresThrowable() {
return ignoresThrowable;
}
public void activateOptions() {
fieldLabels = new FieldLabels();
if (includedFields != null) {
String[] included = SEP_PATTERN.split(includedFields);
for (String val : included) {
fieldLabels.enable(val);
}
}
if (fieldsVal != null) {
String[] fields = SEP_PATTERN.split(fieldsVal);
for (String fieldVal : fields) {
String[] field = PAIR_SEP_PATTERN.split(fieldVal);
this.fields.put(field[0], field[1]);
}
}
if (hostName == null) {
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
hostName = "localhost";
LogLog.error("Unable to determine name of the localhost", e);
}
}
ignoresThrowable = !fieldLabels.exception.isEnabled;
}
@Override
public String getContentType() {
return "application/json";
}
private void appendQuotedName(StringBuilder out, Object name) {
out.append('\"');
appendValue(out, String.valueOf(name));
out.append('\"');
}
private void appendQuotedValue(StringBuilder out, Object val) {
out.append('\"');
appendValue(out, String.valueOf(val));
out.append('\"');
}
private void appendValue(StringBuilder out, String val) {
for (int i = 0, len = val.length(); i < len; i++) {
appendChar(out, val.charAt(i));
}
}
private void appendField(StringBuilder out, Object name, Object val) {
appendQuotedName(out, name);
out.append(':');
appendQuotedValue(out, val);
}
private void appendChar(StringBuilder out, char ch) {
switch (ch) {
case '"':
out.append("\\\"");
break;
case '\\':
out.append("\\\\");
break;
case '/':
out.append("\\/");
break;
case '\b':
out.append("\\b");
break;
case '\f':
out.append("\\f");
break;
case '\n':
out.append("\\n");
break;
case '\r':
out.append("\\r");
break;
case '\t':
out.append("\\t");
break;
default:
if ((ch <= '\u001F') || ('\u007F' <= ch && ch <= '\u009F') || ('\u2000' <= ch && ch <= '\u20FF')) {
out.append("\\u")
.append(HEX_CHARS[ch >> 12 & 0x000F])
.append(HEX_CHARS[ch >> 8 & 0x000F])
.append(HEX_CHARS[ch >> 4 & 0x000F])
.append(HEX_CHARS[ch & 0x000F]);
} else {
out.append(ch);
}
break;
}
}
public void setFields(String fields) {
this.fieldsVal = fields;
}
public void setIncludedFields(String includedFields) {
this.includedFields = includedFields;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
}
Here is the configuration to add in log4j.properties file
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.logger.com.demo.logger=debug, A1
log4j.appender.A1.File=${catalina.base}/jsonlog.log
log4j.appender.A1.Append=true
log4j.appender.A1.layout=com.demo.logger.JsonLayout
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.AppenderAttachable;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
public class JsonLayout extends Layout {
private static final Pattern SEP_PATTERN = Pattern.compile("(?:\\p{Space}*?[,;]\\p{Space}*)+");
private static final Pattern PAIR_SEP_PATTERN = Pattern.compile("(?:\\p{Space}*?[:=]\\p{Space}*)+");
private static final char[] HEX_CHARS =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private class LoggerField {
private String defaultLabel;
private String renderedLabel;
private boolean isEnabled = true;
LoggerField(String defaultName) {
this.defaultLabel = defaultName;
this.renderedLabel = defaultName;
}
LoggerField(String defaultName, String renderedLabel) {
this.defaultLabel = defaultName;
this.renderedLabel = renderedLabel;
}
void enable() {
isEnabled = true;
}
}
private class FieldLabels {
private final ArrayList<LoggerField> allFields = new ArrayList<LoggerField>();
final LoggerField exceptionClass = loggerfield("exception.class", "class");
final LoggerField exceptionMessage = loggerfield("exception.message", "message");
final LoggerField exceptionStacktrace = loggerfield("exception.stacktrace", "stacktrace");
final LoggerField exception = loggerfield("exception");
final LoggerField level = loggerfield("level");
final LoggerField logger = loggerfield("logger");
final LoggerField message = loggerfield("message");
final LoggerField host = loggerfield("host");
final LoggerField path = loggerfield("path");
final LoggerField timestamp = loggerfield("timestamp");
final LoggerField thread = loggerfield("thread");
final LoggerField version = loggerfield("version");
private LoggerField loggerfield(String defaultName) {
return loggerfield(defaultName, null);
}
private LoggerField loggerfield(String defaultName, String renderedLabel) {
LoggerField loggerField;
if(renderedLabel != null) {
loggerField = new LoggerField(defaultName, renderedLabel);
} else {
loggerField = new LoggerField(defaultName);
}
allFields.add(loggerField);
return loggerField;
}
void enable(String fieldName) {
for (LoggerField field : allFields) {
if (field.defaultLabel.startsWith(fieldName)) {
field.enable();
}
}
}
}
private static final String VERSION = "1";
private String fieldsVal;
private String includedFields;
private final Map<String, String> fields;
private FieldLabels fieldLabels = new FieldLabels();
private final DateFormat dateFormat;
private final Date date;
private final StringBuilder buffer;
private String path;
private boolean pathResolved;
private String hostName;
private boolean ignoresThrowable;
public JsonLayout2() {
fields = new HashMap<String, String>();
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
date = new Date();
buffer = new StringBuilder(32*1024);
}
@Override
public String format(LoggingEvent event) {
buffer.setLength(0);
buffer.append('{');
boolean hasPrevField = false;
if (fieldLabels.exception.isEnabled) {
hasPrevField = appendException(buffer, event);
}
if (hasPrevField) {
buffer.append(',');
}
hasPrevField = appendFields(buffer, event);
if (fieldLabels.level.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.level.renderedLabel, event.getLevel().toString());
hasPrevField = true;
}
if (fieldLabels.logger.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.logger.renderedLabel, event.getLoggerName());
hasPrevField = true;
}
if (fieldLabels.message.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.message.renderedLabel, event.getRenderedMessage());
hasPrevField = true;
}
if (fieldLabels.host.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.host.renderedLabel, hostName);
hasPrevField = true;
}
if (fieldLabels.path.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
hasPrevField = appendSourcePath(buffer, event);
}
if (fieldLabels.timestamp.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
date.setTime(event.getTimeStamp());
appendField(buffer, fieldLabels.timestamp.renderedLabel, dateFormat.format(date));
hasPrevField = true;
}
if (fieldLabels.thread.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.thread.renderedLabel, event.getThreadName());
hasPrevField = true;
}
if (fieldLabels.version.isEnabled) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.version.renderedLabel, VERSION);
}
buffer.append("}\n");
return buffer.toString();
}
@SuppressWarnings("UnusedParameters")
private boolean appendFields(StringBuilder buf, LoggingEvent event) {
if (fields.isEmpty()) {
return false;
}
for (Iterator<Map.Entry<String, String>> iter = fields.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry<String, String> entry = iter.next();
appendField(buf, entry.getKey(), entry.getValue());
if (iter.hasNext()) {
buf.append(',');
}
}
return true;
}
private boolean appendSourcePath(StringBuilder buf, LoggingEvent event) {
if (!pathResolved) {
@SuppressWarnings("unchecked")
Appender appender = findLayoutAppender(event.getLogger());
if (appender instanceof FileAppender) {
FileAppender fileAppender = (FileAppender) appender;
path = getAppenderPath(fileAppender);
}
pathResolved = true;
}
if (path != null) {
appendField(buf, fieldLabels.path.renderedLabel, path);
return true;
}
return false;
}
private Appender findLayoutAppender(Category logger) {
for(Category parent = logger; parent != null; parent = parent.getParent()) {
@SuppressWarnings("unchecked")
Appender appender = findLayoutAppender(parent.getAllAppenders());
if(appender != null) {
return appender;
}
}
return null;
}
@SuppressWarnings("unchecked")
private Appender findLayoutAppender(Enumeration<? extends Appender> appenders) {
if(appenders == null) {
return null;
}
while (appenders.hasMoreElements()) {
Appender appender = appenders.nextElement();
// get the first appender with this layout instance and ignore others;
// actually a single instance of this class is not intended to be used with multiple threads.
if (appender.getLayout() == this) {
return appender;
}
if (appender instanceof AppenderAttachable) {
AppenderAttachable appenderContainer = (AppenderAttachable) appender;
return findLayoutAppender(appenderContainer.getAllAppenders());
}
}
return null;
}
private String getAppenderPath(FileAppender fileAppender) {
String path = null;
try {
String fileName = fileAppender.getFile();
if (fileName != null && !fileName.isEmpty()) {
path = new File(fileName).getCanonicalPath();
}
} catch (IOException e) {
LogLog.error("Unable to retrieve appender's file name", e);
}
return path;
}
private boolean appendException(StringBuilder buffer, LoggingEvent event) {
ThrowableInformation throwableInfo = event.getThrowableInformation();
if (throwableInfo == null) {
return false;
}
appendQuotedName(buffer, fieldLabels.exception.renderedLabel);
buffer.append(":{");
boolean hasPrevField = false;
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
Throwable throwable = throwableInfo.getThrowable();
if (throwable != null) {
String message = throwable.getMessage();
if (message != null) {
appendField(buffer, fieldLabels.exceptionMessage.renderedLabel, message);
hasPrevField = true;
}
String className = throwable.getClass().getCanonicalName();
if (className != null) {
if (hasPrevField) {
buffer.append(',');
}
appendField(buffer, fieldLabels.exceptionClass.renderedLabel, className);
hasPrevField = true;
}
}
String[] stackTrace = throwableInfo.getThrowableStrRep();
if (stackTrace != null && stackTrace.length != 0) {
if (hasPrevField) {
buffer.append(',');
}
appendQuotedName(buffer, fieldLabels.exceptionStacktrace.renderedLabel);
buffer.append(":\"");
for (int i = 0, len = stackTrace.length; i < len; i++) {
appendValue(buffer, stackTrace[i]);
if (i != len - 1) {
appendChar(buffer, '\n');
}
}
buffer.append('\"');
}
buffer.append('}');
return true;
}
@Override
public boolean ignoresThrowable() {
return ignoresThrowable;
}
public void activateOptions() {
fieldLabels = new FieldLabels();
if (includedFields != null) {
String[] included = SEP_PATTERN.split(includedFields);
for (String val : included) {
fieldLabels.enable(val);
}
}
if (fieldsVal != null) {
String[] fields = SEP_PATTERN.split(fieldsVal);
for (String fieldVal : fields) {
String[] field = PAIR_SEP_PATTERN.split(fieldVal);
this.fields.put(field[0], field[1]);
}
}
if (hostName == null) {
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
hostName = "localhost";
LogLog.error("Unable to determine name of the localhost", e);
}
}
ignoresThrowable = !fieldLabels.exception.isEnabled;
}
@Override
public String getContentType() {
return "application/json";
}
private void appendQuotedName(StringBuilder out, Object name) {
out.append('\"');
appendValue(out, String.valueOf(name));
out.append('\"');
}
private void appendQuotedValue(StringBuilder out, Object val) {
out.append('\"');
appendValue(out, String.valueOf(val));
out.append('\"');
}
private void appendValue(StringBuilder out, String val) {
for (int i = 0, len = val.length(); i < len; i++) {
appendChar(out, val.charAt(i));
}
}
private void appendField(StringBuilder out, Object name, Object val) {
appendQuotedName(out, name);
out.append(':');
appendQuotedValue(out, val);
}
private void appendChar(StringBuilder out, char ch) {
switch (ch) {
case '"':
out.append("\\\"");
break;
case '\\':
out.append("\\\\");
break;
case '/':
out.append("\\/");
break;
case '\b':
out.append("\\b");
break;
case '\f':
out.append("\\f");
break;
case '\n':
out.append("\\n");
break;
case '\r':
out.append("\\r");
break;
case '\t':
out.append("\\t");
break;
default:
if ((ch <= '\u001F') || ('\u007F' <= ch && ch <= '\u009F') || ('\u2000' <= ch && ch <= '\u20FF')) {
out.append("\\u")
.append(HEX_CHARS[ch >> 12 & 0x000F])
.append(HEX_CHARS[ch >> 8 & 0x000F])
.append(HEX_CHARS[ch >> 4 & 0x000F])
.append(HEX_CHARS[ch & 0x000F]);
} else {
out.append(ch);
}
break;
}
}
public void setFields(String fields) {
this.fieldsVal = fields;
}
public void setIncludedFields(String includedFields) {
this.includedFields = includedFields;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
}
Here is the configuration to add in log4j.properties file
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.logger.com.demo.logger=debug, A1
log4j.appender.A1.File=${catalina.base}/jsonlog.log
log4j.appender.A1.Append=true
log4j.appender.A1.layout=com.demo.logger.JsonLayout
Comments
Post a Comment