package org.apache.logging.log4j.core.appender;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LoggingException;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.AppenderControl;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAliases;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.core.util.Constants;
@Plugin(name = "Failover", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public final class FailoverAppender extends AbstractAppender {
private static final int DEFAULT_INTERVAL_SECONDS = 60;
private final String primaryRef;
private final String[] failovers;
private final Configuration config;
private AppenderControl primary;
private final List<AppenderControl> failoverAppenders = new ArrayList<>();
private final long intervalNanos;
private volatile long nextCheckNanos = 0;
private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers,
final int intervalMillis, final Configuration config, final boolean ignoreExceptions,
final Property[] properties) {
super(name, filter, null, ignoreExceptions, properties);
this.primaryRef = primary;
this.failovers = failovers;
this.config = config;
this.intervalNanos = TimeUnit.MILLISECONDS.toNanos(intervalMillis);
}
@Override
public void start() {
final Map<String, Appender> map = config.getAppenders();
int errors = 0;
final Appender appender = map.get(primaryRef);
if (appender != null) {
primary = new AppenderControl(appender, null, null);
} else {
LOGGER.error("Unable to locate primary Appender " + primaryRef);
++errors;
}
for (final String name : failovers) {
final Appender foAppender = map.get(name);
if (foAppender != null) {
failoverAppenders.add(new AppenderControl(foAppender, null, null));
} else {
LOGGER.error("Failover appender " + name + " is not configured");
}
}
if (failoverAppenders.isEmpty()) {
LOGGER.error("No failover appenders are available");
++errors;
}
if (errors == 0) {
super.start();
}
}
@Override
public void append(final LogEvent event) {
if (!isStarted()) {
error("FailoverAppender " + getName() + " did not start successfully");
return;
}
final long localCheckNanos = nextCheckNanos;
if (localCheckNanos == 0 || System.nanoTime() - localCheckNanos > 0) {
callAppender(event);
} else {
failover(event, null);
}
}
private void callAppender(final LogEvent event) {
try {
primary.callAppender(event);
nextCheckNanos = 0;
} catch (final Exception ex) {
nextCheckNanos = System.nanoTime() + intervalNanos;
failover(event, ex);
}
}
private void failover(final LogEvent event, final Exception ex) {
final RuntimeException re = ex != null ?
(ex instanceof LoggingException ? (LoggingException) ex : new LoggingException(ex)) : null;
boolean written = false;
Exception failoverException = null;
for (final AppenderControl control : failoverAppenders) {
try {
control.callAppender(event);
written = true;
break;
} catch (final Exception fex) {
if (failoverException == null) {
failoverException = fex;
}
}
}
if (!written && !ignoreExceptions()) {
if (re != null) {
throw re;
}
throw new LoggingException("Unable to write to failover appenders", failoverException);
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(getName());
sb.append(" primary=").append(primary).append(", failover={");
boolean first = true;
for (final String str : failovers) {
if (!first) {
sb.append(", ");
}
sb.append(str);
first = false;
}
sb.append('}');
return sb.toString();
}
@PluginFactory
public static FailoverAppender createAppender(
@PluginAttribute("name") final String name,
@PluginAttribute("primary") final String primary,
@PluginElement("Failovers") final String[] failovers,
@PluginAliases("retryInterval")
@PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds,
@PluginConfiguration final Configuration config,
@PluginElement("Filter") final Filter filter,
@PluginAttribute("ignoreExceptions") final String ignore) {
if (name == null) {
LOGGER.error("A name for the Appender must be specified");
return null;
}
if (primary == null) {
LOGGER.error("A primary Appender must be specified");
return null;
}
if (failovers == null || failovers.length == 0) {
LOGGER.error("At least one failover Appender must be specified");
return null;
}
final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS);
int retryIntervalMillis;
if (seconds >= 0) {
retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS;
} else {
LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default");
retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS;
}
final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions, null);
}
}