All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.moocar.logbackgelf.GelfAppender Maven / Gradle / Ivy

Go to download

GELF Appender for logback. Use this appender to log messages to a graylog2 server via GELF messages.

There is a newer version: 0.12
Show newest version
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