1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender;
18
19 import org.apache.logging.log4j.LoggingException;
20 import org.apache.logging.log4j.core.Appender;
21 import org.apache.logging.log4j.core.Filter;
22 import org.apache.logging.log4j.core.LogEvent;
23 import org.apache.logging.log4j.core.config.AppenderControl;
24 import org.apache.logging.log4j.core.config.Configuration;
25 import org.apache.logging.log4j.core.config.plugins.Plugin;
26 import org.apache.logging.log4j.core.config.plugins.PluginAttr;
27 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
28 import org.apache.logging.log4j.core.config.plugins.PluginElement;
29 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
30 import org.apache.logging.log4j.core.helpers.Constants;
31
32 import java.io.Serializable;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.Map;
36
37
38
39
40
41
42 @Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true)
43 public final class FailoverAppender<T extends Serializable> extends AbstractAppender<T> {
44
45 private static final int DEFAULT_INTERVAL = 60 * Constants.MILLIS_IN_SECONDS;
46
47 private final String primaryRef;
48
49 private final String[] failovers;
50
51 private final Configuration config;
52
53 private AppenderControl<?> primary;
54
55 private final List<AppenderControl<?>> failoverAppenders = new ArrayList<AppenderControl<?>>();
56
57 private final long interval;
58
59 private long nextCheck = 0;
60
61 private volatile boolean failure = false;
62
63 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers,
64 final int interval, final Configuration config, final boolean handleExceptions) {
65 super(name, filter, null, handleExceptions);
66 this.primaryRef = primary;
67 this.failovers = failovers;
68 this.config = config;
69 this.interval = interval;
70 }
71
72
73 @Override
74 @SuppressWarnings("unchecked")
75 public void start() {
76 final Map<String, Appender<?>> map = config.getAppenders();
77 int errors = 0;
78 if (map.containsKey(primaryRef)) {
79 primary = new AppenderControl(map.get(primaryRef), null, null);
80 } else {
81 LOGGER.error("Unable to locate primary Appender " + primaryRef);
82 ++errors;
83 }
84 for (final String name : failovers) {
85 if (map.containsKey(name)) {
86 failoverAppenders.add(new AppenderControl(map.get(name), null, null));
87 } else {
88 LOGGER.error("Failover appender " + name + " is not configured");
89 }
90 }
91 if (failoverAppenders.size() == 0) {
92 LOGGER.error("No failover appenders are available");
93 ++errors;
94 }
95 if (errors == 0) {
96 super.start();
97 }
98 }
99
100
101
102
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
174
175
176
177
178
179
180
181
182
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 }