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.util.ArrayList;
33 import java.util.List;
34 import java.util.Map;
35
36
37
38
39
40
41 @Plugin(name = "Failover", type = "Core", elementType = "appender", printObject = true)
42 public final class FailoverAppender extends AbstractAppender {
43
44 private static final int DEFAULT_INTERVAL = 60 * Constants.MILLIS_IN_SECONDS;
45
46 private final String primaryRef;
47
48 private final String[] failovers;
49
50 private final Configuration config;
51
52 private AppenderControl primary;
53
54 private final List<AppenderControl> failoverAppenders = new ArrayList<AppenderControl>();
55
56 private final long interval;
57
58 private long nextCheck = 0;
59
60 private volatile boolean failure = false;
61
62 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers,
63 final int interval, final Configuration config, final boolean handleExceptions) {
64 super(name, filter, null, handleExceptions);
65 this.primaryRef = primary;
66 this.failovers = failovers;
67 this.config = config;
68 this.interval = interval;
69 }
70
71
72 @Override
73 public void start() {
74 final Map<String, Appender<?>> map = config.getAppenders();
75 int errors = 0;
76 if (map.containsKey(primaryRef)) {
77 primary = new AppenderControl(map.get(primaryRef), null, null);
78 } else {
79 LOGGER.error("Unable to locate primary Appender " + primaryRef);
80 ++errors;
81 }
82 for (final String name : failovers) {
83 if (map.containsKey(name)) {
84 failoverAppenders.add(new AppenderControl(map.get(name), null, null));
85 } else {
86 LOGGER.error("Failover appender " + name + " is not configured");
87 }
88 }
89 if (failoverAppenders.size() == 0) {
90 LOGGER.error("No failover appenders are available");
91 ++errors;
92 }
93 if (errors == 0) {
94 super.start();
95 }
96 }
97
98
99
100
101
102 public void append(final LogEvent event) {
103 final RuntimeException re = null;
104 if (!isStarted()) {
105 error("FailoverAppender " + getName() + " did not start successfully");
106 return;
107 }
108 if (!failure) {
109 callAppender(event);
110 } else {
111 final long current = System.currentTimeMillis();
112 if (current >= nextCheck) {
113 callAppender(event);
114 } else {
115 failover(event, null);
116 }
117 }
118 }
119
120 private void callAppender(final LogEvent event) {
121 try {
122 primary.callAppender(event);
123 } catch (final Exception ex) {
124 nextCheck = System.currentTimeMillis() + interval;
125 failure = true;
126 failover(event, ex);
127 }
128 }
129
130 private void failover(final LogEvent event, final Exception ex) {
131 final RuntimeException re = ex != null ? 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 && !isExceptionSuppressed()) {
146 if (re != null) {
147 throw re;
148 } else {
149 throw new LoggingException("Unable to write to failover appenders", failoverException);
150 }
151 }
152 }
153
154 @Override
155 public String toString() {
156 final StringBuilder sb = new StringBuilder(getName());
157 sb.append(" primary=").append(primary).append(", failover={");
158 boolean first = true;
159 for (final String str : failovers) {
160 if (!first) {
161 sb.append(", ");
162 }
163 sb.append(str);
164 first = false;
165 }
166 sb.append("}");
167 return sb.toString();
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181
182 @PluginFactory
183 public static FailoverAppender createAppender(@PluginAttr("name") final String name,
184 @PluginAttr("primary") final String primary,
185 @PluginElement("failovers") final String[] failovers,
186 @PluginAttr("retryInterval") final String interval,
187 @PluginConfiguration final Configuration config,
188 @PluginElement("filters") final Filter filter,
189 @PluginAttr("suppressExceptions") final String suppress) {
190 if (name == null) {
191 LOGGER.error("A name for the Appender must be specified");
192 return null;
193 }
194 if (primary == null) {
195 LOGGER.error("A primary Appender must be specified");
196 return null;
197 }
198 if (failovers == null || failovers.length == 0) {
199 LOGGER.error("At least one failover Appender must be specified");
200 return null;
201 }
202
203 int retryInterval;
204 if (interval == null) {
205 retryInterval = DEFAULT_INTERVAL;
206 } else {
207 try {
208 final int value = Integer.parseInt(interval);
209 if (value >= 0) {
210 retryInterval = value * Constants.MILLIS_IN_SECONDS;
211 } else {
212 LOGGER.warn("Interval " + interval + " is less than zero. Using default");
213 retryInterval = DEFAULT_INTERVAL;
214 }
215 } catch (final NumberFormatException nfe) {
216 LOGGER.error("Interval " + interval + " is non-numeric. Using default");
217 retryInterval = DEFAULT_INTERVAL;
218 }
219 }
220
221 final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
222
223 return new FailoverAppender(name, filter, primary, failovers, retryInterval, config, handleExceptions);
224 }
225 }