GelfAppenderFactory.java

package net.gini.dropwizard.gelf.logging;

import biz.paluch.logging.gelf.intern.GelfMessage;
import biz.paluch.logging.gelf.logback.GelfLogbackAppender;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.helpers.NOPAppender;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.dropwizard.logging.AbstractAppenderFactory;
import io.dropwizard.logging.async.AsyncAppenderFactory;
import io.dropwizard.logging.filter.LevelFilterFactory;
import io.dropwizard.logging.layout.LayoutFactory;
import io.dropwizard.validation.PortRange;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;

@JsonTypeName("gelf")
public class GelfAppenderFactory extends AbstractAppenderFactory<ILoggingEvent> {

    @JsonProperty
    private boolean enabled = true;

    @JsonProperty
    private Optional<String> facility = Optional.empty();

    @JsonProperty
    @NotEmpty
    private String host = "localhost";

    @JsonProperty
    @PortRange
    private int port = 12201;

    @JsonProperty
    private Optional<String> originHost = Optional.empty();

    @JsonProperty
    @NotNull
    private ImmutableMap<String, String> additionalFields = ImmutableMap.of();

    @JsonProperty
    @NotNull
    private ImmutableMap<String, String> additionalFieldTypes = ImmutableMap.of();

    @JsonProperty
    private boolean includeFullMDC = false;

    @JsonProperty
    private boolean includeLocation = true;

    @JsonProperty
    @NotNull
    private Collection<String> mdcFields = ImmutableList.of();

    @JsonProperty
    @NotNull
    private Collection<String> dynamicMdcFields = ImmutableList.of();

    @JsonProperty
    private boolean mdcProfiling = false;

    @JsonProperty
    private boolean extractStackTrace = false;

    @JsonProperty
    private boolean filterStackTrace = false;

    @JsonProperty
    @Min(0)
    private int maximumMessageSize = 8192;

    @JsonProperty
    @NotNull
    private String timestampPattern = "yyyy-MM-dd HH:mm:ss,SSSS";

    public Optional<String> getFacility() {
        return facility;
    }

    public void setFacility(Optional<String> facility) {
        this.facility = facility;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public Optional<String> getOriginHost() {
        return originHost;
    }

    public void setOriginHost(Optional<String> originHost) {
        this.originHost = originHost;
    }

    public ImmutableMap<String, String> getAdditionalFields() {
        return additionalFields;
    }

    public void setAdditionalFields(ImmutableMap<String, String> additionalFields) {
        this.additionalFields = additionalFields;
    }

    public ImmutableMap<String, String> getAdditionalFieldTypes() {
        return additionalFieldTypes;
    }

    public void setAdditionalFieldTypes(ImmutableMap<String, String> additionalFieldTypes) {
        this.additionalFieldTypes = additionalFieldTypes;
    }

    public boolean isIncludeFullMDC() {
        return includeFullMDC;
    }

    public void setIncludeFullMDC(boolean includeFullMDC) {
        this.includeFullMDC = includeFullMDC;
    }

    public boolean isIncludeLocation() {
        return includeLocation;
    }

    public void setIncludeLocation(boolean includeLocation) {
        this.includeLocation = includeLocation;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(final boolean enabled) {
        this.enabled = enabled;
    }

    public Collection<String> getMdcFields() {
        return mdcFields;
    }

    public void setMdcFields(Collection<String> mdcFields) {
        this.mdcFields = mdcFields;
    }

    public Collection<String> getDynamicMdcFields() {
        return dynamicMdcFields;
    }

    public void setDynamicMdcFields(Collection<String> dynamicMdcFields) {
        this.dynamicMdcFields = dynamicMdcFields;
    }

    public boolean isMdcProfiling() {
        return mdcProfiling;
    }

    public void setMdcProfiling(boolean mdcProfiling) {
        this.mdcProfiling = mdcProfiling;
    }

    public boolean isExtractStackTrace() {
        return extractStackTrace;
    }

    public void setExtractStackTrace(boolean extractStackTrace) {
        this.extractStackTrace = extractStackTrace;
    }

    public boolean isFilterStackTrace() {
        return filterStackTrace;
    }

    public void setFilterStackTrace(boolean filterStackTrace) {
        this.filterStackTrace = filterStackTrace;
    }

    public int getMaximumMessageSize() {
        return maximumMessageSize;
    }

    public void setMaximumMessageSize(int maximumMessageSize) {
        this.maximumMessageSize = maximumMessageSize;
    }

    public String getTimestampPattern() {
        return timestampPattern;
    }

    public void setTimestampPattern(String timestampPattern) {
        this.timestampPattern = timestampPattern;
    }

    @Override
    public Appender<ILoggingEvent> build(LoggerContext context,
                                         String applicationName,
                                         LayoutFactory<ILoggingEvent> layoutFactory,
                                         LevelFilterFactory<ILoggingEvent> levelFilterFactory,
                                         AsyncAppenderFactory<ILoggingEvent> asyncAppenderFactory) {
        if (!enabled) {
            final Appender<ILoggingEvent> appender = new NOPAppender<>();
            appender.start();
            return appender;
        }

        final GelfLogbackAppender appender = new GelfLogbackAppender();

        appender.setContext(context);
        appender.setName("dropwizard-gelf");
        appender.setFacility(facility.orElse(applicationName));
        appender.setGraylogHost(host);
        appender.setGraylogPort(port);
        appender.setVersion(GelfMessage.GELF_VERSION_1_1);
        appender.setAdditionalFields(buildFieldsSpec(additionalFields));
        appender.setAdditionalFieldTypes(buildFieldsSpec(additionalFieldTypes));
        appender.setMdcFields(buildMdcFieldsSpec(mdcFields));
        appender.setDynamicMdcFields(buildMdcFieldsSpec(dynamicMdcFields));
        appender.setIncludeFullMdc(includeFullMDC);
        appender.setIncludeLocation(includeLocation);
        appender.setMdcProfiling(mdcProfiling);
        appender.setExtractStackTrace(Boolean.toString(extractStackTrace));
        appender.setFilterStackTrace(filterStackTrace);
        appender.setMaximumMessageSize(maximumMessageSize);
        appender.setTimestampPattern(timestampPattern);

        originHost.ifPresent(appender::setOriginHost);

        appender.addFilter(levelFilterFactory.build(threshold));
        getFilterFactories().forEach(f -> appender.addFilter(f.build()));
        appender.start();

        return wrapAsync(appender, asyncAppenderFactory);
    }

    private String buildMdcFieldsSpec(@NotNull Collection<String> fields) {
        return Joiner.on(',').skipNulls().join(fields);
    }

    private String buildFieldsSpec(@NotNull Map<String, String> fields) {
        return Joiner.on(',').withKeyValueSeparator("=").useForNull("null").join(fields);
    }
}