/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache license, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the license for the specific language governing permissions and * limitations under the license. */ 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.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; /** * The FailoverAppender will capture exceptions in an Appender and then route the event * to a different appender. Hopefully it is obvious that the Appenders must be configured * to not suppress exceptions for the FailoverAppender to work. */ @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) { super(name, filter, null, ignoreExceptions); 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(); } } /** * Handle the Log event. * @param event The LogEvent. */ @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(); } /** * Create a Failover Appender. * @param name The name of the Appender (required). * @param primary The name of the primary Appender (required). * @param failovers The name of one or more Appenders to fail over to (at least one is required). * @param retryIntervalSeconds The retry interval in seconds. * @param config The current Configuration (passed by the Configuration when the appender is created). * @param filter A Filter (optional). * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise * they are propagated to the caller. * @return The FailoverAppender that was created. */ @PluginFactory public static FailoverAppender createAppender( @PluginAttribute("name") final String name, @PluginAttribute("primary") final String primary, @PluginElement("Failovers") final String[] failovers, @PluginAliases("retryInterval") // deprecated @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); } }