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.util.Booleans;
35 import org.apache.logging.log4j.core.util.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 volatile long nextCheckMillis = 0;
60
61 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers,
62 final int intervalMillis, final Configuration config, final boolean ignoreExceptions) {
63 super(name, filter, null, ignoreExceptions);
64 this.primaryRef = primary;
65 this.failovers = failovers;
66 this.config = config;
67 this.intervalMillis = intervalMillis;
68 }
69
70
71 @Override
72 public void start() {
73 final Map<String, Appender> map = config.getAppenders();
74 int errors = 0;
75 if (map.containsKey(primaryRef)) {
76 primary = new AppenderControl(map.get(primaryRef), null, null);
77 } else {
78 LOGGER.error("Unable to locate primary Appender " + primaryRef);
79 ++errors;
80 }
81 for (final String name : failovers) {
82 if (map.containsKey(name)) {
83 failoverAppenders.add(new AppenderControl(map.get(name), null, null));
84 } else {
85 LOGGER.error("Failover appender " + name + " is not configured");
86 }
87 }
88 if (failoverAppenders.isEmpty()) {
89 LOGGER.error("No failover appenders are available");
90 ++errors;
91 }
92 if (errors == 0) {
93 super.start();
94 }
95 }
96
97
98
99
100
101 @Override
102 public void append(final LogEvent event) {
103 if (!isStarted()) {
104 error("FailoverAppender " + getName() + " did not start successfully");
105 return;
106 }
107 long localCheckMillis = nextCheckMillis;
108 if (localCheckMillis == 0 || System.currentTimeMillis() > localCheckMillis) {
109 callAppender(event);
110 } else {
111 failover(event, null);
112 }
113 }
114
115 private void callAppender(final LogEvent event) {
116 try {
117 primary.callAppender(event);
118 nextCheckMillis = 0;
119 } catch (final Exception ex) {
120 nextCheckMillis = System.currentTimeMillis() + intervalMillis;
121 failover(event, ex);
122 }
123 }
124
125 private void failover(final LogEvent event, final Exception ex) {
126 final RuntimeException re = ex != null ?
127 (ex instanceof LoggingException ? (LoggingException)ex : new LoggingException(ex)) : null;
128 boolean written = false;
129 Exception failoverException = null;
130 for (final AppenderControl control : failoverAppenders) {
131 try {
132 control.callAppender(event);
133 written = true;
134 break;
135 } catch (final Exception fex) {
136 if (failoverException == null) {
137 failoverException = fex;
138 }
139 }
140 }
141 if (!written && !ignoreExceptions()) {
142 if (re != null) {
143 throw re;
144 } else {
145 throw new LoggingException("Unable to write to failover appenders", failoverException);
146 }
147 }
148 }
149
150 @Override
151 public String toString() {
152 final StringBuilder sb = new StringBuilder(getName());
153 sb.append(" primary=").append(primary).append(", failover={");
154 boolean first = true;
155 for (final String str : failovers) {
156 if (!first) {
157 sb.append(", ");
158 }
159 sb.append(str);
160 first = false;
161 }
162 sb.append('}');
163 return sb.toString();
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178 @PluginFactory
179 public static FailoverAppender createAppender(
180 @PluginAttribute("name") final String name,
181 @PluginAttribute("primary") final String primary,
182 @PluginElement("Failovers") final String[] failovers,
183 @PluginAttribute("retryInterval") final String retryIntervalString,
184 @PluginConfiguration final Configuration config,
185 @PluginElement("Filters") final Filter filter,
186 @PluginAttribute("ignoreExceptions") final String ignore) {
187 if (name == null) {
188 LOGGER.error("A name for the Appender must be specified");
189 return null;
190 }
191 if (primary == null) {
192 LOGGER.error("A primary Appender must be specified");
193 return null;
194 }
195 if (failovers == null || failovers.length == 0) {
196 LOGGER.error("At least one failover Appender must be specified");
197 return null;
198 }
199
200 final int seconds = parseInt(retryIntervalString, DEFAULT_INTERVAL_SECONDS);
201 int retryIntervalMillis;
202 if (seconds >= 0) {
203 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS;
204 } else {
205 LOGGER.warn("Interval " + retryIntervalString + " is less than zero. Using default");
206 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS;
207 }
208
209 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
210
211 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions);
212 }
213 }