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 import java.util.concurrent.TimeUnit;
23
24 import org.apache.logging.log4j.LoggingException;
25 import org.apache.logging.log4j.core.Appender;
26 import org.apache.logging.log4j.core.Filter;
27 import org.apache.logging.log4j.core.LogEvent;
28 import org.apache.logging.log4j.core.config.AppenderControl;
29 import org.apache.logging.log4j.core.config.Configuration;
30 import org.apache.logging.log4j.core.config.plugins.Plugin;
31 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
32 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
33 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
34 import org.apache.logging.log4j.core.config.plugins.PluginElement;
35 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
36 import org.apache.logging.log4j.core.util.Booleans;
37 import org.apache.logging.log4j.core.util.Constants;
38
39
40
41
42
43
44 @Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true)
45 public final class FailoverAppender extends AbstractAppender {
46
47 private static final int DEFAULT_INTERVAL_SECONDS = 60;
48
49 private final String primaryRef;
50
51 private final String[] failovers;
52
53 private final Configuration config;
54
55 private AppenderControl primary;
56
57 private final List<AppenderControl> failoverAppenders = new ArrayList<>();
58
59 private final long intervalNanos;
60
61 private volatile long nextCheckNanos = 0;
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.intervalNanos = TimeUnit.MILLISECONDS.toNanos(intervalMillis);
70 }
71
72
73 @Override
74 public void start() {
75 final Map<String, Appender> map = config.getAppenders();
76 int errors = 0;
77 final Appender appender = map.get(primaryRef);
78 if (appender != null) {
79 primary = new AppenderControl(appender, null, null);
80 } else {
81 LOGGER.error("Unable to locate primary Appender " + primaryRef);
82 ++errors;
83 }
84 for (final String name : failovers) {
85 final Appender foAppender = map.get(name);
86 if (foAppender != null) {
87 failoverAppenders.add(new AppenderControl(foAppender, null, null));
88 } else {
89 LOGGER.error("Failover appender " + name + " is not configured");
90 }
91 }
92 if (failoverAppenders.isEmpty()) {
93 LOGGER.error("No failover appenders are available");
94 ++errors;
95 }
96 if (errors == 0) {
97 super.start();
98 }
99 }
100
101
102
103
104
105 @Override
106 public void append(final LogEvent event) {
107 if (!isStarted()) {
108 error("FailoverAppender " + getName() + " did not start successfully");
109 return;
110 }
111 final long localCheckNanos = nextCheckNanos;
112 if (localCheckNanos == 0 || System.nanoTime() - localCheckNanos > 0) {
113 callAppender(event);
114 } else {
115 failover(event, null);
116 }
117 }
118
119 private void callAppender(final LogEvent event) {
120 try {
121 primary.callAppender(event);
122 nextCheckNanos = 0;
123 } catch (final Exception ex) {
124 nextCheckNanos = System.nanoTime() + intervalNanos;
125 failover(event, ex);
126 }
127 }
128
129 private void failover(final LogEvent event, final Exception ex) {
130 final RuntimeException re = ex != null ?
131 (ex instanceof LoggingException ? (LoggingException) ex : 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 && !ignoreExceptions()) {
146 if (re != null) {
147 throw re;
148 }
149 throw new LoggingException("Unable to write to failover appenders", failoverException);
150 }
151 }
152
153 @Override
154 public String toString() {
155 final StringBuilder sb = new StringBuilder(getName());
156 sb.append(" primary=").append(primary).append(", failover={");
157 boolean first = true;
158 for (final String str : failovers) {
159 if (!first) {
160 sb.append(", ");
161 }
162 sb.append(str);
163 first = false;
164 }
165 sb.append('}');
166 return sb.toString();
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180
181 @PluginFactory
182 public static FailoverAppender createAppender(
183 @PluginAttribute("name") final String name,
184 @PluginAttribute("primary") final String primary,
185 @PluginElement("Failovers") final String[] failovers,
186 @PluginAliases("retryInterval")
187 @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds,
188 @PluginConfiguration final Configuration config,
189 @PluginElement("Filter") final Filter filter,
190 @PluginAttribute("ignoreExceptions") final String ignore) {
191 if (name == null) {
192 LOGGER.error("A name for the Appender must be specified");
193 return null;
194 }
195 if (primary == null) {
196 LOGGER.error("A primary Appender must be specified");
197 return null;
198 }
199 if (failovers == null || failovers.length == 0) {
200 LOGGER.error("At least one failover Appender must be specified");
201 return null;
202 }
203
204 final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS);
205 int retryIntervalMillis;
206 if (seconds >= 0) {
207 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS;
208 } else {
209 LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default");
210 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS;
211 }
212
213 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
214
215 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions);
216 }
217 }