package io.dropwizard.logging; import ch.qos.logback.classic.AsyncAppender; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.Appender; import ch.qos.logback.core.AsyncAppenderBase; import ch.qos.logback.core.Context; import ch.qos.logback.core.pattern.PatternLayoutBase; import ch.qos.logback.core.spi.DeferredProcessingAware; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import io.dropwizard.logging.async.AsyncAppenderFactory; import io.dropwizard.logging.filter.FilterFactory; import io.dropwizard.logging.layout.LayoutFactory; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import java.util.List; import java.util.TimeZone; import static com.google.common.base.Strings.nullToEmpty; /** * A base implementation of {@link AppenderFactory}. * <p/> * <b>Configuration Parameters:</b> * <table> * <tr> * <td>Name</td> * <td>Default</td> * <td>Description</td> * </tr> * <tr> * <td>{@code threshold}</td> * <td>ALL</td> * <td>The minimum event level the appender will handle.</td> * </tr> * <tr> * <td>{@code logFormat}</td> * <td>(none)</td> * <td>An appender-specific log format.</td> * </tr> * <tr> * <td>{@code timeZone}</td> * <td>{@code UTC}</td> * <td> * The time zone to which event timestamps will be converted. * Ignored if logFormat is supplied. * </td> * </tr> * <tr> * <td>{@code queueSize}</td> * <td>{@link AsyncAppenderBase}</td> * <td>The maximum capacity of the blocking queue.</td> * </tr> * <tr> * <td>{@code includeCallerData}</td> * <td>{@link AsyncAppenderBase}</td> * <td> * Whether to include caller data, required for line numbers. * Beware, is considered expensive. * </td> * </tr> * <tr> * <td>{@code discardingThreshold}</td> * <td>{@link AsyncAppenderBase}</td> * <td> * By default, when the blocking queue has 20% capacity remaining, * it will drop events of level TRACE, DEBUG and INFO, keeping only * events of level WARN and ERROR. To keep all events, set discardingThreshold to 0. * </td> * </tr> * <tr> * <td>{@code filterFactories}</td> * <td>(none)</td> * <td> * A list of {@link FilterFactory filters} to apply to the appender, in order, * after the {@code threshold}. * </td> * </tr> * </table> */ public abstract class AbstractAppenderFactory<E extends DeferredProcessingAware> implements AppenderFactory<E> { @NotNull protected Level threshold = Level.ALL; protected String logFormat; @NotNull protected TimeZone timeZone = TimeZone.getTimeZone("UTC"); @Min(1) @Max(Integer.MAX_VALUE) private int queueSize = AsyncAppenderBase.DEFAULT_QUEUE_SIZE; private int discardingThreshold = -1; private boolean includeCallerData = false; private ImmutableList<FilterFactory<E>> filterFactories = ImmutableList.of(); private boolean neverBlock = false; @JsonProperty public int getQueueSize() { return queueSize; } @JsonProperty public void setQueueSize(int queueSize) { this.queueSize = queueSize; } @JsonProperty public int getDiscardingThreshold() { return discardingThreshold; } @JsonProperty public void setDiscardingThreshold(int discardingThreshold) { this.discardingThreshold = discardingThreshold; } @JsonProperty public Level getThreshold() { return threshold; } @JsonProperty public void setThreshold(Level threshold) { this.threshold = threshold; } @JsonProperty public String getLogFormat() { return logFormat; } @JsonProperty public void setLogFormat(String logFormat) { this.logFormat = logFormat; } @JsonProperty public TimeZone getTimeZone() { return timeZone; } @JsonProperty public void setTimeZone(String zoneId) { this.timeZone = nullToEmpty(zoneId).equalsIgnoreCase("system") ? TimeZone.getDefault() : TimeZone.getTimeZone(zoneId); } @JsonProperty public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } @JsonProperty public boolean isIncludeCallerData() { return includeCallerData; } @JsonProperty public void setIncludeCallerData(boolean includeCallerData) { this.includeCallerData = includeCallerData; } @JsonProperty public ImmutableList<FilterFactory<E>> getFilterFactories() { return filterFactories; } @JsonProperty public void setFilterFactories(List<FilterFactory<E>> appenders) { this.filterFactories = ImmutableList.copyOf(appenders); } @JsonProperty public void setNeverBlock(boolean neverBlock) { this.neverBlock = neverBlock; } protected Appender<E> wrapAsync(Appender<E> appender, AsyncAppenderFactory<E> asyncAppenderFactory) { return wrapAsync(appender, asyncAppenderFactory, appender.getContext()); } protected Appender<E> wrapAsync(Appender<E> appender, AsyncAppenderFactory<E> asyncAppenderFactory, Context context) { final AsyncAppenderBase<E> asyncAppender = asyncAppenderFactory.build(); if (asyncAppender instanceof AsyncAppender) { ((AsyncAppender) asyncAppender).setIncludeCallerData(includeCallerData); } asyncAppender.setQueueSize(queueSize); asyncAppender.setDiscardingThreshold(discardingThreshold); asyncAppender.setContext(context); asyncAppender.setName("async-" + appender.getName()); asyncAppender.addAppender(appender); asyncAppender.setNeverBlock(neverBlock); asyncAppender.start(); return asyncAppender; } protected PatternLayoutBase<E> buildLayout(LoggerContext context, LayoutFactory<E> layoutFactory) { final PatternLayoutBase<E> formatter = layoutFactory.build(context, timeZone); if (!Strings.isNullOrEmpty(logFormat)) { formatter.setPattern(logFormat); } formatter.start(); return formatter; } }