| /* |
| * 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.Property; |
| import org.apache.logging.log4j.plugins.Plugin; |
| import org.apache.logging.log4j.plugins.PluginAliases; |
| import org.apache.logging.log4j.plugins.PluginAttribute; |
| import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; |
| import org.apache.logging.log4j.plugins.PluginElement; |
| import org.apache.logging.log4j.plugins.PluginFactory; |
| import org.apache.logging.log4j.core.util.Booleans; |
| import org.apache.logging.log4j.core.util.Constants; |
| import org.apache.logging.log4j.plugins.validation.constraints.Required; |
| |
| /** |
| * 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; |
| |
| private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers, |
| final int intervalMillis, final Configuration config, final boolean ignoreExceptions, |
| 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(); |
| } |
| } |
| |
| /** |
| * 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 @Required(message = "A name for the Appender must be specified") final String name, |
| @PluginAttribute @Required(message = "A primary Appender must be specified") final String primary, |
| @PluginElement @Required(message = "At least one failover Appender must be specified") final String[] failovers, |
| @PluginAliases("retryInterval") // deprecated |
| @PluginAttribute(defaultInt = DEFAULT_INTERVAL_SECONDS) final int retryIntervalSeconds, |
| @PluginConfiguration final Configuration config, |
| @PluginElement final Filter filter, |
| @PluginAttribute(defaultBoolean = true) final boolean ignoreExceptions) { |
| |
| int retryIntervalMillis; |
| if (retryIntervalSeconds >= 0) { |
| retryIntervalMillis = retryIntervalSeconds * Constants.MILLIS_IN_SECONDS; |
| } else { |
| LOGGER.warn("Interval {} is less than zero. Using default", retryIntervalSeconds); |
| retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS; |
| } |
| return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions, Property.EMPTY_ARRAY); |
| } |
| } |