001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.appender; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Map; 022import java.util.concurrent.TimeUnit; 023 024import org.apache.logging.log4j.LoggingException; 025import org.apache.logging.log4j.core.Appender; 026import org.apache.logging.log4j.core.Filter; 027import org.apache.logging.log4j.core.LogEvent; 028import org.apache.logging.log4j.core.config.AppenderControl; 029import org.apache.logging.log4j.core.config.Configuration; 030import org.apache.logging.log4j.core.config.plugins.Plugin; 031import org.apache.logging.log4j.core.config.plugins.PluginAliases; 032import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 033import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 034import org.apache.logging.log4j.core.config.plugins.PluginElement; 035import org.apache.logging.log4j.core.config.plugins.PluginFactory; 036import org.apache.logging.log4j.core.util.Booleans; 037import org.apache.logging.log4j.core.util.Constants; 038 039/** 040 * The FailoverAppender will capture exceptions in an Appender and then route the event 041 * to a different appender. Hopefully it is obvious that the Appenders must be configured 042 * to not suppress exceptions for the FailoverAppender to work. 043 */ 044@Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true) 045public final class FailoverAppender extends AbstractAppender { 046 047 private static final int DEFAULT_INTERVAL_SECONDS = 60; 048 049 private final String primaryRef; 050 051 private final String[] failovers; 052 053 private final Configuration config; 054 055 private AppenderControl primary; 056 057 private final List<AppenderControl> failoverAppenders = new ArrayList<>(); 058 059 private final long intervalNanos; 060 061 private volatile long nextCheckNanos = 0; 062 063 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers, 064 final int intervalMillis, final Configuration config, final boolean ignoreExceptions) { 065 super(name, filter, null, ignoreExceptions); 066 this.primaryRef = primary; 067 this.failovers = failovers; 068 this.config = config; 069 this.intervalNanos = TimeUnit.MILLISECONDS.toNanos(intervalMillis); 070 } 071 072 073 @Override 074 public void start() { 075 final Map<String, Appender> map = config.getAppenders(); 076 int errors = 0; 077 final Appender appender = map.get(primaryRef); 078 if (appender != null) { 079 primary = new AppenderControl(appender, null, null); 080 } else { 081 LOGGER.error("Unable to locate primary Appender " + primaryRef); 082 ++errors; 083 } 084 for (final String name : failovers) { 085 final Appender foAppender = map.get(name); 086 if (foAppender != null) { 087 failoverAppenders.add(new AppenderControl(foAppender, null, null)); 088 } else { 089 LOGGER.error("Failover appender " + name + " is not configured"); 090 } 091 } 092 if (failoverAppenders.isEmpty()) { 093 LOGGER.error("No failover appenders are available"); 094 ++errors; 095 } 096 if (errors == 0) { 097 super.start(); 098 } 099 } 100 101 /** 102 * Handle the Log event. 103 * @param event The LogEvent. 104 */ 105 @Override 106 public void append(final LogEvent event) { 107 if (!isStarted()) { 108 error("FailoverAppender " + getName() + " did not start successfully"); 109 return; 110 } 111 final long localCheckNanos = nextCheckNanos; 112 if (localCheckNanos == 0 || System.nanoTime() - localCheckNanos > 0) { 113 callAppender(event); 114 } else { 115 failover(event, null); 116 } 117 } 118 119 private void callAppender(final LogEvent event) { 120 try { 121 primary.callAppender(event); 122 nextCheckNanos = 0; 123 } catch (final Exception ex) { 124 nextCheckNanos = System.nanoTime() + intervalNanos; 125 failover(event, ex); 126 } 127 } 128 129 private void failover(final LogEvent event, final Exception ex) { 130 final RuntimeException re = ex != null ? 131 (ex instanceof LoggingException ? (LoggingException) ex : new LoggingException(ex)) : null; 132 boolean written = false; 133 Exception failoverException = null; 134 for (final AppenderControl control : failoverAppenders) { 135 try { 136 control.callAppender(event); 137 written = true; 138 break; 139 } catch (final Exception fex) { 140 if (failoverException == null) { 141 failoverException = fex; 142 } 143 } 144 } 145 if (!written && !ignoreExceptions()) { 146 if (re != null) { 147 throw re; 148 } 149 throw new LoggingException("Unable to write to failover appenders", failoverException); 150 } 151 } 152 153 @Override 154 public String toString() { 155 final StringBuilder sb = new StringBuilder(getName()); 156 sb.append(" primary=").append(primary).append(", failover={"); 157 boolean first = true; 158 for (final String str : failovers) { 159 if (!first) { 160 sb.append(", "); 161 } 162 sb.append(str); 163 first = false; 164 } 165 sb.append('}'); 166 return sb.toString(); 167 } 168 169 /** 170 * Create a Failover Appender. 171 * @param name The name of the Appender (required). 172 * @param primary The name of the primary Appender (required). 173 * @param failovers The name of one or more Appenders to fail over to (at least one is required). 174 * @param retryIntervalSeconds The retry interval in seconds. 175 * @param config The current Configuration (passed by the Configuration when the appender is created). 176 * @param filter A Filter (optional). 177 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 178 * they are propagated to the caller. 179 * @return The FailoverAppender that was created. 180 */ 181 @PluginFactory 182 public static FailoverAppender createAppender( 183 @PluginAttribute("name") final String name, 184 @PluginAttribute("primary") final String primary, 185 @PluginElement("Failovers") final String[] failovers, 186 @PluginAliases("retryInterval") // deprecated 187 @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds, 188 @PluginConfiguration final Configuration config, 189 @PluginElement("Filter") final Filter filter, 190 @PluginAttribute("ignoreExceptions") final String ignore) { 191 if (name == null) { 192 LOGGER.error("A name for the Appender must be specified"); 193 return null; 194 } 195 if (primary == null) { 196 LOGGER.error("A primary Appender must be specified"); 197 return null; 198 } 199 if (failovers == null || failovers.length == 0) { 200 LOGGER.error("At least one failover Appender must be specified"); 201 return null; 202 } 203 204 final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS); 205 int retryIntervalMillis; 206 if (seconds >= 0) { 207 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS; 208 } else { 209 LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default"); 210 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS; 211 } 212 213 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 214 215 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions); 216 } 217}