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 java.util.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22
23 import org.apache.logging.log4j.LoggingException;
24 import org.apache.logging.log4j.core.Appender;
25 import org.apache.logging.log4j.core.Filter;
26 import org.apache.logging.log4j.core.LogEvent;
27 import org.apache.logging.log4j.core.config.AppenderControl;
28 import org.apache.logging.log4j.core.config.Configuration;
29 import org.apache.logging.log4j.core.config.plugins.Plugin;
30 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
31 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
32 import org.apache.logging.log4j.core.config.plugins.PluginElement;
33 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
34 import org.apache.logging.log4j.core.helpers.Booleans;
35 import org.apache.logging.log4j.core.helpers.Constants;
36
37
38
39
40
41
42 @Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true)
43 public final class FailoverAppender extends AbstractAppender {
44
45 private static final int DEFAULT_INTERVAL_SECONDS = 60;
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 intervalMillis;
58
59 private long nextCheckMillis = 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 intervalMillis, final Configuration config, final boolean ignoreExceptions) {
65 super(name, filter, null, ignoreExceptions);
66 this.primaryRef = primary;
67 this.failovers = failovers;
68 this.config = config;
69 this.intervalMillis = intervalMillis;
70 }
71
72
73 @Override
74 public void start() {
75 final Map<String, Appender> map = config.getAppenders();
76 int errors = 0;
77 if (map.containsKey(primaryRef)) {
78 primary = new AppenderControl(map.get(primaryRef), null, null);
79 } else {
80 LOGGER.error("Unable to locate primary Appender " + primaryRef);
81 ++errors;
82 }
83 for (final String name : failovers) {
84 if (map.containsKey(name)) {
85 failoverAppenders.add(new AppenderControl(map.get(name), null, null));
86 } else {
87 LOGGER.error("Failover appender " + name + " is not configured");
88 }
89 }
90 if (failoverAppenders.size() == 0) {
91 LOGGER.error("No failover appenders are available");
92 ++errors;
93 }
94 if (errors == 0) {
95 super.start();
96 }
97 }
98
99
100
101
102
103 @Override
104 public void append(final LogEvent event) {
105 if (!isStarted()) {
106 error("FailoverAppender " + getName() + " did not start successfully");
107 return;
108 }
109 if (!failure) {
110 callAppender(event);
111 } else {
112 final long currentMillis = System.currentTimeMillis();
113 if (currentMillis >= nextCheckMillis) {
114 callAppender(event);
115 } else {
116 failover(event, null);
117 }
118 }
119 }
120
121 private void callAppender(final LogEvent event) {
122 try {
123 primary.callAppender(event);
124 } catch (final Exception ex) {
125 nextCheckMillis = System.currentTimeMillis() + intervalMillis;
126 failure = true;
127 failover(event, ex);
128 }
129 }
130
131 private void failover(final LogEvent event, final Exception ex) {
132 final RuntimeException re = ex != null ?
133 (ex instanceof LoggingException ? (LoggingException)ex : 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 && !ignoreExceptions()) {
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 FailoverAppender createAppender(
186 @PluginAttribute("name") final String name,
187 @PluginAttribute("primary") final String primary,
188 @PluginElement("Failovers") final String[] failovers,
189 @PluginAttribute("retryInterval") final String retryIntervalString,
190 @PluginConfiguration final Configuration config,
191 @PluginElement("Filters") final Filter filter,
192 @PluginAttribute("ignoreExceptions") final String ignore) {
193 if (name == null) {
194 LOGGER.error("A name for the Appender must be specified");
195 return null;
196 }
197 if (primary == null) {
198 LOGGER.error("A primary Appender must be specified");
199 return null;
200 }
201 if (failovers == null || failovers.length == 0) {
202 LOGGER.error("At least one failover Appender must be specified");
203 return null;
204 }
205
206 final int seconds = parseInt(retryIntervalString, DEFAULT_INTERVAL_SECONDS);
207 int retryIntervalMillis;
208 if (seconds >= 0) {
209 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS;
210 } else {
211 LOGGER.warn("Interval " + retryIntervalString + " is less than zero. Using default");
212 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS;
213 }
214
215 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
216
217 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions);
218 }
219 }