io.dropwizard.logging.FileAppenderFactory Maven / Gradle / Ivy
package io.dropwizard.logging;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.OutputStreamAppender;
import ch.qos.logback.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
import ch.qos.logback.core.rolling.TimeBasedFileNamingAndTriggeringPolicy;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import ch.qos.logback.core.util.FileSize;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.dropwizard.util.DataSize;
import io.dropwizard.validation.MinDataSize;
import io.dropwizard.validation.ValidationMethod;
import javax.annotation.Nullable;
import javax.validation.constraints.Min;
import static java.util.Objects.requireNonNull;
/**
* An {@link AppenderFactory} implementation which provides an appender that writes events to a file, archiving older
* files as it goes.
*
* Configuration Parameters:
*
*
* Name
* Default
* Description
*
*
* {@code type}
* REQUIRED
* The appender type. Must be {@code file}.
*
*
* {@code threshold}
* {@code ALL}
* The lowest level of events to write to the file.
*
*
* {@code currentLogFilename}
* REQUIRED
* The filename where current events are logged.
*
*
* {@code archive}
* {@code true}
* Whether or not to archive old events in separate files.
*
*
* {@code archivedLogFilenamePattern}
* REQUIRED if {@code archive} is {@code true}.
*
* The filename pattern for archived files.
* If {@code maxFileSize} is specified, rollover is size-based, and the pattern must contain {@code %i} for
* an integer index of the archived file.
* Otherwise rollover is date-based, and the pattern must contain {@code %d}, which is replaced with the
* date in {@code yyyy-MM-dd} form.
* If the pattern ends with {@code .gz} or {@code .zip}, files will be compressed as they are archived.
*
*
*
* {@code archivedFileCount}
* {@code 5}
*
* The number of archived files to keep. Must be greater than or equal to {@code 0}. Zero is a
* special value signifying to keep infinite logs (use with caution)
*
*
*
* {@code maxFileSize}
* (unlimited)
*
* The maximum size of the currently active file before a rollover is triggered. The value can be expressed
* with SI and IEC prefixes, see {@link io.dropwizard.util.DataSizeUnit}.
* Examples include 100MiB, 1GiB, 1TiB. Sizes can also be spelled out, such as 100 mebibytes,
* 1 gibibyte, 1 tebibyte.
*
*
*
* {@code totalSizeCap}
* (unlimited)
*
* Controls the total size of all files. Oldest archives are deleted asynchronously when the total
* size cap is exceeded.
*
*
*
* {@code timeZone}
* {@code UTC}
* The time zone to which event timestamps will be converted.
*
*
* {@code logFormat}
* the default format
*
* The Logback pattern with which events will be formatted. See
* the Logback documentation
* for details.
*
*
*
* {@code bufferSize}
* 8KiB
*
* The buffer size of the underlying FileAppender (setting added in logback 1.1.10). Increasing this from
* the default of 8KiB to 256KiB is reported to significantly reduce thread contention.
*
*
*
* {@code immediateFlush}
* {@code true}
*
* If set to true, log events will be immediately flushed to disk. Immediate flushing is safer, but
* it degrades logging throughput.
* See the Logback documentation
* for details.
*
*
*
*
* @see AbstractAppenderFactory
*/
@JsonTypeName("file")
public class FileAppenderFactory extends AbstractOutputStreamAppenderFactory {
@Nullable
private String currentLogFilename;
private boolean archive = true;
@Nullable
private String archivedLogFilenamePattern;
@Min(0)
private int archivedFileCount = 5;
@Nullable
private DataSize maxFileSize;
@Nullable
private DataSize totalSizeCap;
@MinDataSize(1)
private DataSize bufferSize = DataSize.bytes(FileAppender.DEFAULT_BUFFER_SIZE);
private boolean immediateFlush = true;
@JsonProperty
@Nullable
public String getCurrentLogFilename() {
return currentLogFilename;
}
@JsonProperty
public void setCurrentLogFilename(@Nullable String currentLogFilename) {
this.currentLogFilename = currentLogFilename;
}
@JsonProperty
public boolean isArchive() {
return archive;
}
@JsonProperty
public void setArchive(boolean archive) {
this.archive = archive;
}
@JsonProperty
@Nullable
public String getArchivedLogFilenamePattern() {
return archivedLogFilenamePattern;
}
@JsonProperty
public void setArchivedLogFilenamePattern(String archivedLogFilenamePattern) {
this.archivedLogFilenamePattern = archivedLogFilenamePattern;
}
@JsonProperty
public int getArchivedFileCount() {
return archivedFileCount;
}
@JsonProperty
public void setArchivedFileCount(int archivedFileCount) {
this.archivedFileCount = archivedFileCount;
}
@JsonProperty
@Nullable
public DataSize getMaxFileSize() {
return maxFileSize;
}
@JsonProperty
public void setMaxFileSize(@Nullable DataSize maxFileSize) {
this.maxFileSize = maxFileSize;
}
/**
* @since 2.0
*/
@JsonProperty
@Nullable
public DataSize getTotalSizeCap() {
return totalSizeCap;
}
/**
* @since 2.0
*/
@JsonProperty
public void setTotalSizeCap(@Nullable DataSize totalSizeCap) {
this.totalSizeCap = totalSizeCap;
}
@JsonProperty
public DataSize getBufferSize() {
return bufferSize;
}
@JsonProperty
public void setBufferSize(DataSize bufferSize) {
this.bufferSize = bufferSize;
}
public boolean isImmediateFlush() {
return immediateFlush;
}
@JsonProperty
public void setImmediateFlush(boolean immediateFlush) {
this.immediateFlush = immediateFlush;
}
/**
* @since 2.0
*/
@JsonIgnore
@ValidationMethod(message = "totalSizeCap has no effect when using maxFileSize and an archivedLogFilenamePattern without %d, as archivedFileCount implicitly controls the total size cap")
public boolean isTotalSizeCapValid() {
return !archive || totalSizeCap == null ||
!(maxFileSize != null && !requireNonNull(archivedLogFilenamePattern).contains("%d"));
}
@JsonIgnore
@ValidationMethod(message = "must have archivedLogFilenamePattern if archive is true")
public boolean isValidArchiveConfiguration() {
return !archive || (archivedLogFilenamePattern != null);
}
@JsonIgnore
@ValidationMethod(message = "when specifying maxFileSize, archivedLogFilenamePattern must contain %i")
public boolean isValidForMaxFileSizeSetting() {
return !archive || maxFileSize == null ||
(archivedLogFilenamePattern != null && archivedLogFilenamePattern.contains("%i"));
}
@JsonIgnore
@ValidationMethod(message = "when archivedLogFilenamePattern contains %i, maxFileSize must be specified")
public boolean isMaxFileSizeSettingSpecified() {
return !archive || !(archivedLogFilenamePattern != null && archivedLogFilenamePattern.contains("%i")) ||
maxFileSize != null;
}
@JsonIgnore
@ValidationMethod(message = "currentLogFilename can only be null when archiving is enabled")
public boolean isValidFileConfiguration() {
return archive || currentLogFilename != null;
}
@Override
protected OutputStreamAppender appender(LoggerContext context) {
final FileAppender appender = buildAppender(context);
appender.setName("file-appender");
appender.setAppend(true);
appender.setContext(context);
appender.setImmediateFlush(immediateFlush);
appender.setPrudent(false);
return appender;
}
protected FileAppender buildAppender(LoggerContext context) {
if (archive) {
final RollingFileAppender appender = new RollingFileAppender<>();
appender.setContext(context);
appender.setFile(currentLogFilename);
appender.setBufferSize(new FileSize(bufferSize.toBytes()));
if (maxFileSize != null && !requireNonNull(archivedLogFilenamePattern).contains("%d")) {
final FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
rollingPolicy.setContext(context);
rollingPolicy.setMaxIndex(getArchivedFileCount());
rollingPolicy.setFileNamePattern(getArchivedLogFilenamePattern());
rollingPolicy.setParent(appender);
rollingPolicy.start();
appender.setRollingPolicy(rollingPolicy);
final SizeBasedTriggeringPolicy triggeringPolicy = new SizeBasedTriggeringPolicy<>();
triggeringPolicy.setMaxFileSize(new FileSize(maxFileSize.toBytes()));
triggeringPolicy.setContext(context);
triggeringPolicy.start();
appender.setTriggeringPolicy(triggeringPolicy);
return appender;
} else {
final TimeBasedRollingPolicy rollingPolicy;
if (maxFileSize == null) {
rollingPolicy = new TimeBasedRollingPolicy<>();
final TimeBasedFileNamingAndTriggeringPolicy triggeringPolicy = new DefaultTimeBasedFileNamingAndTriggeringPolicy<>();
triggeringPolicy.setContext(context);
triggeringPolicy.setTimeBasedRollingPolicy(rollingPolicy);
appender.setTriggeringPolicy(triggeringPolicy);
} else {
// Creating a size and time policy does not need a separate triggering policy set
// on the appender because this policy registers the trigger policy
final SizeAndTimeBasedRollingPolicy sizeAndTimeBasedRollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
sizeAndTimeBasedRollingPolicy.setMaxFileSize(new FileSize(maxFileSize.toBytes()));
rollingPolicy = sizeAndTimeBasedRollingPolicy;
}
if (totalSizeCap != null) {
rollingPolicy.setTotalSizeCap(new FileSize(totalSizeCap.toBytes()));
}
rollingPolicy.setContext(context);
rollingPolicy.setFileNamePattern(archivedLogFilenamePattern);
rollingPolicy.setMaxHistory(archivedFileCount);
appender.setRollingPolicy(rollingPolicy);
rollingPolicy.setParent(appender);
rollingPolicy.start();
return appender;
}
}
final FileAppender appender = new FileAppender<>();
appender.setContext(context);
appender.setFile(currentLogFilename);
appender.setBufferSize(new FileSize(bufferSize.toBytes()));
return appender;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy