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.PluginAliases;
31 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
32 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
33 import org.apache.logging.log4j.core.config.plugins.PluginElement;
34 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
35 import org.apache.logging.log4j.core.util.Booleans;
36 import org.apache.logging.log4j.core.util.Constants;
37
38
39
40
41
42
43 @Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true)
44 public final class FailoverAppender extends AbstractAppender {
45
46 private static final long serialVersionUID = 1L;
47
48 private static final int DEFAULT_INTERVAL_SECONDS = 60;
49
50 private final String primaryRef;
51
52 private final String[] failovers;
53
54 private final Configuration config;
55
56 private AppenderControl primary;
57
58 private final List<AppenderControl> failoverAppenders = new ArrayList<AppenderControl>();
59
60 private final long intervalMillis;
61
62 private volatile long nextCheckMillis = 0;
63
64 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers,
65 final int intervalMillis, final Configuration config, final boolean ignoreExceptions) {
66 super(name, filter, null, ignoreExceptions);
67 this.primaryRef = primary;
68 this.failovers = failovers;
69 this.config = config;
70 this.intervalMillis = intervalMillis;
71 }
72
73
74 @Override
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.isEmpty()) {
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 final long localCheckMillis = nextCheckMillis;
111 if (localCheckMillis == 0 || System.currentTimeMillis() > localCheckMillis) {
112 callAppender(event);
113 } else {
114 failover(event, null);
115 }
116 }
117
118 private void callAppender(final LogEvent event) {
119 try {
120 primary.callAppender(event);
121 nextCheckMillis = 0;
122 } catch (final Exception ex) {
123 nextCheckMillis = System.currentTimeMillis() + intervalMillis;
124 failover(event, ex);
125 }
126 }
127
128 private void failover(final LogEvent event, final Exception ex) {
129 final RuntimeException re = ex != null ?
130 (ex instanceof LoggingException ? (LoggingException)ex : new LoggingException(ex)) : null;
131 boolean written = false;
132 Exception failoverException = null;
133 for (final AppenderControl control : failoverAppenders) {
134 try {
135 control.callAppender(event);
136 written = true;
137 break;
138 } catch (final Exception fex) {
139 if (failoverException == null) {
140 failoverException = fex;
141 }
142 }
143 }
144 if (!written && !ignoreExceptions()) {
145 if (re != null) {
146 throw re;
147 }
148 throw new LoggingException("Unable to write to failover appenders", failoverException);
149 }
150 }
151
152 @Override
153 public String toString() {
154 final StringBuilder sb = new StringBuilder(getName());
155 sb.append(" primary=").append(primary).append(", failover={");
156 boolean first = true;
157 for (final String str : failovers) {
158 if (!first) {
159 sb.append(", ");
160 }
161 sb.append(str);
162 first = false;
163 }
164 sb.append('}');
165 return sb.toString();
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179
180 @PluginFactory
181 public static FailoverAppender createAppender(
182 @PluginAttribute("name") final String name,
183 @PluginAttribute("primary") final String primary,
184 @PluginElement("Failovers") final String[] failovers,
185 @PluginAliases("retryInterval")
186 @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds,
187 @PluginConfiguration final Configuration config,
188 @PluginElement("Filter") final Filter filter,
189 @PluginAttribute("ignoreExceptions") final String ignore) {
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 final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS);
204 int retryIntervalMillis;
205 if (seconds >= 0) {
206 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS;
207 } else {
208 LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default");
209 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS;
210 }
211
212 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
213
214 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions);
215 }
216 }