me.moocar.logbackgelf.GelfAppender Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of logback-gelf Show documentation
Show all versions of logback-gelf Show documentation
GELF Appender for logback. Use this appender to log messages to a graylog2 server via GELF messages.
package me.moocar.logbackgelf;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import static me.moocar.logbackgelf.InternetUtils.getInetAddress;
import static me.moocar.logbackgelf.InternetUtils.getLocalHostName;
/**
* Responsible for Formatting a log event and sending it to a Graylog2 Server. Note that you can't swap in a different
* Layout since the GELF format is static.
*/
public class GelfAppender extends AppenderBase {
// The following are configurable via logback configuration
private String facility = "GELF";
private String graylog2ServerHost = "localhost";
private int graylog2ServerPort = 12201;
private boolean useLoggerName = false;
private boolean useMarker = false;
private boolean useThreadName = false;
private String graylog2ServerVersion = "0.9.6";
private int chunkThreshold = 1000;
private String messagePattern = "%m%rEx";
private String shortMessagePattern = null;
private Map additionalFields = new HashMap();
private Map staticAdditionalFields = new HashMap();
private Map fieldTypes = new HashMap();
private boolean includeFullMDC;
private String hostName;
// The following are hidden (not configurable)
private int shortMessageLength = 255;
private static final int maxChunks = 127;
private int messageIdLength = 8;
private boolean padSeq = false;
private final byte[] chunkedGelfId = new byte[]{0x1e, 0x0f};
private AppenderExecutor appenderExecutor;
/**
* The main append method. Takes the event that is being logged, formats if for GELF and then sends it over the wire
* to the log server
*
* @param logEvent The event that we are logging
*/
@Override
protected void append(ILoggingEvent logEvent) {
try {
appenderExecutor.append(logEvent);
} catch (RuntimeException e) {
System.out.println(getStringStackTrace(e));
this.addError("Error occurred: ", e);
throw e;
}
}
private String getStringStackTrace(Exception e) {
Writer result = new StringWriter();
PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
return result.toString();
}
@Override
public void start() {
super.start();
initExecutor();
}
/**
* This is an ad-hoc dependency injection mechanism. We don't want create all these classes every time a message is
* logged. They will hang around for the lifetime of the appender.
*/
private void initExecutor() {
try {
InetAddress address = getInetAddress(graylog2ServerHost);
Transport transport = new Transport(graylog2ServerPort, address);
if (graylog2ServerVersion.equals("0.9.5")) {
messageIdLength = 32;
padSeq = true;
}
if (hostName == null) {
hostName = getLocalHostName();
}
PayloadChunker payloadChunker = new PayloadChunker(chunkThreshold, maxChunks,
new MessageIdProvider(messageIdLength, MessageDigest.getInstance("MD5"), hostName),
new ChunkFactory(chunkedGelfId, padSeq));
GelfConverter converter = new GelfConverter(facility, useLoggerName, useThreadName, useMarker, additionalFields,
fieldTypes, staticAdditionalFields, shortMessageLength, hostName, messagePattern, shortMessagePattern,
includeFullMDC);
appenderExecutor = new AppenderExecutor(transport, payloadChunker, converter, new Zipper(), chunkThreshold);
} catch (Exception e) {
throw new RuntimeException("Error initialising appender appenderExecutor", e);
}
}
//////////// Logback Property Getter/Setters ////////////////
/**
* The name of your service. Appears in facility column in graylog2-web-interface
*/
public String getFacility() {
return facility;
}
public void setFacility(String facility) {
this.facility = facility;
}
/**
* The hostname of the graylog2 server to send messages to
*/
public String getGraylog2ServerHost() {
return graylog2ServerHost;
}
public void setGraylog2ServerHost(String graylog2ServerHost) {
this.graylog2ServerHost = graylog2ServerHost;
}
/**
* The port of the graylog2 server to send messages to
*/
public int getGraylog2ServerPort() {
return graylog2ServerPort;
}
public void setGraylog2ServerPort(int graylog2ServerPort) {
this.graylog2ServerPort = graylog2ServerPort;
}
/**
* 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 hostname using a config option
* @return the local hostname (defaults to getLocalHost() if not overridden
* in config
*/
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
/**
* 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 ||
!GelfConverter.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 " +
GelfConverter.primitiveTypes.keySet() +
")");
}
fieldTypes.put(splitted[0], splitted[1]);
}
public Map getFieldTypes() {
return fieldTypes;
}
public void setFieldTypes(final Map fieldTypes) {
this.fieldTypes = fieldTypes;
}
/**
* The length of the message to truncate to
*/
public int getShortMessageLength() {
return shortMessageLength;
}
public void setShortMessageLength(int shortMessageLength) {
this.shortMessageLength = shortMessageLength;
}
public String getGraylog2ServerVersion() {
return graylog2ServerVersion;
}
public void setGraylog2ServerVersion(String graylog2ServerVersion) {
this.graylog2ServerVersion = graylog2ServerVersion;
}
public int getChunkThreshold() {
return chunkThreshold;
}
public void setChunkThreshold(int chunkThreshold) {
this.chunkThreshold = chunkThreshold;
}
public String getMessagePattern() {
return messagePattern;
}
public void setMessagePattern(String messagePattern) {
this.messagePattern = messagePattern;
}
public String getShortMessagePattern() {
return shortMessagePattern;
}
public void setShortMessagePattern(String shortMessagePattern) {
this.shortMessagePattern = shortMessagePattern;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy