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 */
017 package org.apache.logging.log4j.core.appender;
018
019 import org.apache.logging.log4j.LoggingException;
020 import org.apache.logging.log4j.core.Appender;
021 import org.apache.logging.log4j.core.Filter;
022 import org.apache.logging.log4j.core.LogEvent;
023 import org.apache.logging.log4j.core.config.AppenderControl;
024 import org.apache.logging.log4j.core.config.Configuration;
025 import org.apache.logging.log4j.core.config.plugins.Plugin;
026 import org.apache.logging.log4j.core.config.plugins.PluginAttr;
027 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
028 import org.apache.logging.log4j.core.config.plugins.PluginElement;
029 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
030 import org.apache.logging.log4j.core.helpers.Constants;
031
032 import java.io.Serializable;
033 import java.util.ArrayList;
034 import java.util.List;
035 import java.util.Map;
036
037 /**
038 * The FailoverAppender will capture exceptions in an Appender and then route the event
039 * to a different appender. Hopefully it is obvious that the Appenders must be configured
040 * to not suppress exceptions for the FailoverAppender to work.
041 */
042 @Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true)
043 public final class FailoverAppender<T extends Serializable> extends AbstractAppender<T> {
044
045 private static final int DEFAULT_INTERVAL = 60 * Constants.MILLIS_IN_SECONDS;
046
047 private final String primaryRef;
048
049 private final String[] failovers;
050
051 private final Configuration config;
052
053 private AppenderControl<?> primary;
054
055 private final List<AppenderControl<?>> failoverAppenders = new ArrayList<AppenderControl<?>>();
056
057 private final long interval;
058
059 private long nextCheck = 0;
060
061 private volatile boolean failure = false;
062
063 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers,
064 final int interval, final Configuration config, final boolean handleExceptions) {
065 super(name, filter, null, handleExceptions);
066 this.primaryRef = primary;
067 this.failovers = failovers;
068 this.config = config;
069 this.interval = interval;
070 }
071
072
073 @Override
074 @SuppressWarnings("unchecked")
075 public void start() {
076 final Map<String, Appender<?>> map = config.getAppenders();
077 int errors = 0;
078 if (map.containsKey(primaryRef)) {
079 primary = new AppenderControl(map.get(primaryRef), null, null);
080 } else {
081 LOGGER.error("Unable to locate primary Appender " + primaryRef);
082 ++errors;
083 }
084 for (final String name : failovers) {
085 if (map.containsKey(name)) {
086 failoverAppenders.add(new AppenderControl(map.get(name), null, null));
087 } else {
088 LOGGER.error("Failover appender " + name + " is not configured");
089 }
090 }
091 if (failoverAppenders.size() == 0) {
092 LOGGER.error("No failover appenders are available");
093 ++errors;
094 }
095 if (errors == 0) {
096 super.start();
097 }
098 }
099
100 /**
101 * Handle the Log event.
102 * @param event The LogEvent.
103 */
104 @Override
105 public void append(final LogEvent event) {
106 if (!isStarted()) {
107 error("FailoverAppender " + getName() + " did not start successfully");
108 return;
109 }
110 if (!failure) {
111 callAppender(event);
112 } else {
113 final long current = System.currentTimeMillis();
114 if (current >= nextCheck) {
115 callAppender(event);
116 } else {
117 failover(event, null);
118 }
119 }
120 }
121
122 private void callAppender(final LogEvent event) {
123 try {
124 primary.callAppender(event);
125 } catch (final Exception ex) {
126 nextCheck = System.currentTimeMillis() + interval;
127 failure = true;
128 failover(event, ex);
129 }
130 }
131
132 private void failover(final LogEvent event, final Exception ex) {
133 final RuntimeException re = ex != null ? new LoggingException(ex) : null;
134 boolean written = false;
135 Exception failoverException = null;
136 for (final AppenderControl control : failoverAppenders) {
137 try {
138 control.callAppender(event);
139 written = true;
140 break;
141 } catch (final Exception fex) {
142 if (failoverException == null) {
143 failoverException = fex;
144 }
145 }
146 }
147 if (!written && !isExceptionSuppressed()) {
148 if (re != null) {
149 throw re;
150 } else {
151 throw new LoggingException("Unable to write to failover appenders", failoverException);
152 }
153 }
154 }
155
156 @Override
157 public String toString() {
158 final StringBuilder sb = new StringBuilder(getName());
159 sb.append(" primary=").append(primary).append(", failover={");
160 boolean first = true;
161 for (final String str : failovers) {
162 if (!first) {
163 sb.append(", ");
164 }
165 sb.append(str);
166 first = false;
167 }
168 sb.append("}");
169 return sb.toString();
170 }
171
172 /**
173 * Create a Failover Appender.
174 * @param name The name of the Appender (required).
175 * @param primary The name of the primary Appender (required).
176 * @param failovers The name of one or more Appenders to fail over to (at least one is required).
177 * @param interval The retry interval.
178 * @param config The current Configuration (passed by the Configuration when the appender is created).
179 * @param filter A Filter (optional).
180 * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
181 * The default is "true".
182 * @return The FailoverAppender that was created.
183 */
184 @PluginFactory
185 public static <S extends Serializable> FailoverAppender<S> createAppender(@PluginAttr("name") final String name,
186 @PluginAttr("primary") final String primary,
187 @PluginElement("failovers") final String[] failovers,
188 @PluginAttr("retryInterval") final String interval,
189 @PluginConfiguration final Configuration config,
190 @PluginElement("filters") final Filter filter,
191 @PluginAttr("suppressExceptions") final String suppress) {
192 if (name == null) {
193 LOGGER.error("A name for the Appender must be specified");
194 return null;
195 }
196 if (primary == null) {
197 LOGGER.error("A primary Appender must be specified");
198 return null;
199 }
200 if (failovers == null || failovers.length == 0) {
201 LOGGER.error("At least one failover Appender must be specified");
202 return null;
203 }
204
205 int retryInterval;
206 if (interval == null) {
207 retryInterval = DEFAULT_INTERVAL;
208 } else {
209 try {
210 final int value = Integer.parseInt(interval);
211 if (value >= 0) {
212 retryInterval = value * Constants.MILLIS_IN_SECONDS;
213 } else {
214 LOGGER.warn("Interval " + interval + " is less than zero. Using default");
215 retryInterval = DEFAULT_INTERVAL;
216 }
217 } catch (final NumberFormatException nfe) {
218 LOGGER.error("Interval " + interval + " is non-numeric. Using default");
219 retryInterval = DEFAULT_INTERVAL;
220 }
221 }
222
223 final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
224
225 return new FailoverAppender<S>(name, filter, primary, failovers, retryInterval, config, handleExceptions);
226 }
227 }