
me.moocar.logbackgelf.GelfLayout Maven / Gradle / Ivy
package me.moocar.logbackgelf;
import java.lang.reflect.Method;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.util.LevelToSyslogSeverity;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.LayoutBase;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Responsible for formatting a log event into a GELF JSON string
*/
public class GelfLayout extends LayoutBase {
private final String DEFAULT_FULL_MESSAGE_PATTERN = "%rEx%m";
private final String DEFAULT_SHORT_MESSAGE_PATTERN = "%ex{short}%.100m";
private boolean useLoggerName = false;
private boolean useThreadName = false;
private boolean useMarker = false;
private Map additionalFields = new HashMap();
private Map fieldTypes = new HashMap();
private Map staticAdditionalFields = new HashMap();
private String host = getLocalHostName();
private final Gson gson;
private Layout fullMessageLayout;
private Layout shortMessageLayout;
private boolean includeFullMDC = false;
static Map primitiveTypes;
static {
primitiveTypes = new HashMap();
try {
primitiveTypes.put("int", Integer.class.getDeclaredMethod("parseInt", String.class));
primitiveTypes.put("Integer", Integer.class.getDeclaredMethod("parseInt", String.class));
primitiveTypes.put("long", Long.class.getDeclaredMethod("parseLong", String.class));
primitiveTypes.put("Long", Long.class.getDeclaredMethod("parseLong", String.class));
primitiveTypes.put("float", Float.class.getDeclaredMethod("parseFloat", String.class));
primitiveTypes.put("Float", Float.class.getDeclaredMethod("parseFloat", String.class));
primitiveTypes.put("double", Double.class.getDeclaredMethod("parseDouble", String.class));
primitiveTypes.put("Double", Double.class.getDeclaredMethod("parseDouble", String.class));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public GelfLayout() {
// Init GSON for underscores
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
this.gson = gsonBuilder.create();
}
@Override
public void start() {
if (fullMessageLayout == null) {
this.fullMessageLayout = initNewPatternLayout(DEFAULT_FULL_MESSAGE_PATTERN);
}
if (shortMessageLayout == null) {
this.shortMessageLayout = initNewPatternLayout(DEFAULT_SHORT_MESSAGE_PATTERN);
}
super.start();
}
private PatternLayout initNewPatternLayout(String pattern) {
PatternLayout layout = new PatternLayout();
layout.setPattern(pattern);
layout.setContext(this.getContext());
layout.start();
return layout;
}
@Override
public String doLayout(E event) {
return gson.toJson(mapFields(event));
}
/**
* Creates a map of properties that represent the GELF message.
*
* @param logEvent The log event
* @return map of gelf properties
*/
private Map mapFields(E logEvent) {
Map map = new HashMap();
map.put("host", host);
map.put("full_message", fullMessageLayout.doLayout(logEvent));
map.put("short_message", shortMessageLayout.doLayout(logEvent));
stackTraceField(map, logEvent);
map.put("timestamp", logEvent.getTimeStamp() / 1000.0);
map.put("version", "1.1");
map.put("level", LevelToSyslogSeverity.convert(logEvent));
additionalFields(map, logEvent);
staticAdditionalFields(map);
return map;
}
private void stackTraceField(Map map, ILoggingEvent eventObject) {
IThrowableProxy throwableProxy = eventObject.getThrowableProxy();
if (throwableProxy != null ) {
StackTraceElementProxy[] proxyStackTraces = throwableProxy.getStackTraceElementProxyArray();
if (proxyStackTraces != null && proxyStackTraces.length > 0) {
StackTraceElement[] callStackTraces = eventObject.getCallerData();
if (callStackTraces != null && callStackTraces.length > 0) {
StackTraceElement lastStack = callStackTraces[0];
map.put("_file", lastStack.getFileName());
map.put("_line", String.valueOf(lastStack.getLineNumber()));
}
}
}
}
/**
* Converts the additional fields into proper GELF JSON
*
* @param map The map of additional fields
* @param eventObject The Logging event that we are converting to GELF
*/
private void additionalFields(Map map, ILoggingEvent eventObject) {
if (useLoggerName) {
map.put("_loggerName", eventObject.getLoggerName());
}
if(useMarker && eventHasMarker(eventObject)) {
map.put("_marker", eventObject.getMarker().toString());
}
if (useThreadName) {
map.put("_threadName", eventObject.getThreadName());
}
Map mdc = eventObject.getMDCPropertyMap();
if (mdc != null) {
if (includeFullMDC) {
for (Entry e : mdc.entrySet()) {
if (additionalFields.containsKey(e.getKey())) {
map.put(additionalFields.get(e.getKey()), convertFieldType(e.getValue(), additionalFields.get(e.getKey())));
} else {
map.put("_" + e.getKey(), convertFieldType(e.getValue(), "_" + e.getKey()));
}
}
} else {
for (String key : additionalFields.keySet()) {
String field = mdc.get(key);
if (field != null) {
map.put(additionalFields.get(key), convertFieldType(field, key));
}
}
}
}
}
private Object convertFieldType(Object value, final String type) {
if (primitiveTypes.containsKey(fieldTypes.get(type))) {
try {
value = primitiveTypes.get(fieldTypes.get(type)).invoke(null,
value);
} catch (Exception e1) {
e1.printStackTrace();
}
}
return value;
}
private boolean eventHasMarker(ILoggingEvent eventObject) {
return eventObject.getMarker() != null;
}
private void staticAdditionalFields(Map map) {
for (String key : staticAdditionalFields.keySet()) {
map.put(key, (staticAdditionalFields.get(key)));
}
}
private String getLocalHostName() {
try {
return InternetUtils.getLocalHostName();
} catch (SocketException e) {
return "UNKNOWN";
} catch (UnknownHostException e) {
return "UNKNOWN";
}
}
//////////// Logback Property Getter/Setters ////////////////
/**
* If true, an additional field call "_loggerName" will be added to each gelf message. Its contents will be the
* fully qualified name of the logger. e.g: com.company.Thingo.
*/
public boolean isUseLoggerName() {
return useLoggerName;
}
public void setUseLoggerName(boolean useLoggerName) {
this.useLoggerName = useLoggerName;
}
public boolean isUseMarker() {
return useMarker;
}
public void setUseMarker(boolean useMarker) {
this.useMarker = useMarker;
}
/**
* If true, an additional field call "_threadName" will be added to each gelf message. Its contents will be the
* Name of the thread. Defaults to "false".
*/
public boolean isUseThreadName() {
return useThreadName;
}
public void setUseThreadName(boolean useThreadName) {
this.useThreadName = useThreadName;
}
/**
* additional fields to add to the gelf message. Here's how these work:
Let's take an example. I want to log
* the client's ip address of every request that comes into my web server. To do this, I add the ipaddress to the
* slf4j MDC on each request as follows: ... MDC.put("ipAddress", "44.556.345.657"); ...
Now, to
* include the ip address in the gelf message, i just add the following to my logback.groovy:
* appender("GELF", GelfAppender) { ... additionalFields = [identity:"_identity"] ... }
in the
* additionalFields map, the key is the name of the MDC to look up. the value is the name that should be given to
* the key in the additional field in the gelf message.
*/
public Map getAdditionalFields() {
return additionalFields;
}
public void setAdditionalFields(Map additionalFields) {
this.additionalFields = additionalFields;
}
/**
* static additional fields to add to every gelf message. Key is the additional field key (and should thus begin
* with an underscore). The value is a static string.
*/
public Map getStaticAdditionalFields() {
return staticAdditionalFields;
}
public void setStaticAdditionalFields(Map staticAdditionalFields) {
this.staticAdditionalFields = staticAdditionalFields;
}
/**
* Indicates if all values from the MDC should be included in the gelf
* message or only the once listed as {@link #getAdditionalFields()
* additional fields}.
*
* If true
, the gelf message will contain all values available
* in the MDC. Each MDC key will be converted to a gelf custom field by
* adding an underscore prefix. If an entry exists in
* {@link #getAdditionalFields() additional field} it will be used instead.
*
*
* If false
, only the fields listed in
* {@link #getAdditionalFields() additional field} will be included in the
* message.
*
*
* @return the includeFullMDC
*/
public boolean isIncludeFullMDC() {
return includeFullMDC;
}
public void setIncludeFullMDC(boolean includeFullMDC) {
this.includeFullMDC = includeFullMDC;
}
/**
* Override the local host using a config option
* @return the local host (defaults to getLocalHost() if not overridden
* in config
*/
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
/**
* Add an additional field. This is mainly here for compatibility with logback.xml
*
* @param keyValue This must be in format key:value where key is the MDC key, and value is the GELF field
* name. e.g "ipAddress:_ip_address"
*/
public void addAdditionalField(String keyValue) {
String[] splitted = keyValue.split(":");
if (splitted.length != 2) {
throw new IllegalArgumentException("additionalField must be of the format key:value, where key is the MDC "
+ "key, and value is the GELF field name. But found '" + keyValue + "' instead.");
}
additionalFields.put(splitted[0], splitted[1]);
}
/**
* Add a staticAdditional field. This is mainly here for compatibility with logback.xml
*
* @param keyValue This must be in format key:value where key is the additional field key, and value is a static
* string. e.g "_node_name:www013"
*/
public void addStaticAdditionalField(String keyValue) {
String[] splitted = keyValue.split(":");
if (splitted.length != 2) {
throw new IllegalArgumentException("staticAdditionalField must be of the format key:value, where key is the "
+ "additional field key (therefore should have a leading underscore), and value is a static string. " +
"e.g. _node_name:www013");
}
staticAdditionalFields.put(splitted[0], splitted[1]);
}
public void addFieldType(String keyValue) {
String[] splitted = keyValue.split(":");
if (splitted.length != 2 ||
!GelfLayout.primitiveTypes.containsKey(splitted[1])) {
throw new IllegalArgumentException(
"fieldType must be of the format key:value, where key is the " +
"field key, and value is the type to convert to (one of " +
GelfLayout.primitiveTypes.keySet() +
")");
}
fieldTypes.put(splitted[0], splitted[1]);
}
public Map getFieldTypes() {
return fieldTypes;
}
public void setFieldTypes(final Map fieldTypes) {
this.fieldTypes = fieldTypes;
}
public Layout getFullMessageLayout() {
return fullMessageLayout;
}
public void setFullMessageLayout(Layout fullMessageLayout) {
this.fullMessageLayout = fullMessageLayout;
}
public Layout getShortMessageLayout() {
return shortMessageLayout;
}
public void setShortMessageLayout(Layout shortMessageLayout) {
this.shortMessageLayout = shortMessageLayout;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy