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.LayoutBase;
import ch.qos.logback.core.pattern.PatternLayoutBase;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import io.dropwizard.jackson.Jackson;
import io.dropwizard.logging.async.AsyncAppenderFactory;
import io.dropwizard.logging.filter.FilterFactory;
import io.dropwizard.logging.layout.DiscoverableLayoutFactory;
import io.dropwizard.logging.layout.LayoutFactory;
import javax.annotation.Nullable;
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 AppenderFactory
.
Configuration Parameters:
Name
Default
Description
threshold
ALL
The minimum event level the appender will handle.
logFormat
(none)
An appender-specific log format.
timeZone
UTC
The time zone to which event timestamps will be converted.
Ignored if logFormat is supplied.
queueSize
AsyncAppenderBase
The maximum capacity of the blocking queue.
includeCallerData
AsyncAppenderBase
Whether to include caller data, required for line numbers.
Beware, is considered expensive.
discardingThreshold
AsyncAppenderBase
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.
filterFactories
(none)
A list of filters
to apply to the appender, in order, after the threshold
.
/**
* 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;
@Nullable
protected String logFormat;
@Nullable
protected DiscoverableLayoutFactory layout;
@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 String getThreshold() {
return threshold.toString();
}
@JsonProperty
public void setThreshold(String threshold) {
this.threshold = DefaultLoggingFactory.toLevel(threshold);
}
@JsonProperty
@Nullable
public String getLogFormat() {
return logFormat;
}
@JsonProperty
public void setLogFormat(@Nullable 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;
}
@Nullable
public DiscoverableLayoutFactory getLayout() {
return layout;
}
public void setLayout(@Nullable DiscoverableLayoutFactory layout) {
this.layout = layout;
}
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;
}
@SuppressWarnings("unchecked")
protected LayoutBase<E> buildLayout(LoggerContext context, LayoutFactory<E> defaultLayoutFactory) {
final LayoutBase<E> layoutBase;
if (layout == null) {
final PatternLayoutBase<E> patternLayoutBase = defaultLayoutFactory.build(context, timeZone);
if (!Strings.isNullOrEmpty(logFormat)) {
patternLayoutBase.setPattern(logFormat);
}
layoutBase = patternLayoutBase;
} else {
layoutBase = layout.build(context, timeZone);
}
layoutBase.start();
return layoutBase;
}
}