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.Core;
27 import org.apache.logging.log4j.core.Filter;
28 import org.apache.logging.log4j.core.LogEvent;
29 import org.apache.logging.log4j.core.config.AppenderControl;
30 import org.apache.logging.log4j.core.config.Configuration;
31 import org.apache.logging.log4j.core.config.plugins.Plugin;
32 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
33 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
34 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
35 import org.apache.logging.log4j.core.config.plugins.PluginElement;
36 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
37 import org.apache.logging.log4j.core.util.Booleans;
38 import org.apache.logging.log4j.core.util.Constants;
39
40
41
42
43
44
45 @Plugin(name = "Failover", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
46 public final class FailoverAppender extends AbstractAppender {
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<>();
59
60 private final long intervalNanos;
61
62 private volatile long nextCheckNanos = 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.intervalNanos = TimeUnit.MILLISECONDS.toNanos(intervalMillis);
71 }
72
73
74 @Override
75 public void start() {
76 final Map<String, Appender> map = config.getAppenders();
77 int errors = 0;
78 final Appender appender = map.get(primaryRef);
79 if (appender != null) {
80 primary = new AppenderControl(appender, null, null);
81 } else {
82 LOGGER.error("Unable to locate primary Appender " + primaryRef);
83 ++errors;
84 }
85 for (final String name : failovers) {
86 final Appender foAppender = map.get(name);
87 if (foAppender != null) {
88 failoverAppenders.add(new AppenderControl(foAppender, null, null));
89 } else {
90 LOGGER.error("Failover appender " + name + " is not configured");
91 }
92 }
93 if (failoverAppenders.isEmpty()) {
94 LOGGER.error("No failover appenders are available");
95 ++errors;
96 }
97 if (errors == 0) {
98 super.start();
99 }
100 }
101
102
103
104
105
106 @Override
107 public void append(final LogEvent event) {
108 if (!isStarted()) {
109 error("FailoverAppender " + getName() + " did not start successfully");
110 return;
111 }
112 final long localCheckNanos = nextCheckNanos;
113 if (localCheckNanos == 0 || System.nanoTime() - localCheckNanos > 0) {
114 callAppender(event);
115 } else {
116 failover(event, null);
117 }
118 }
119
120 private void callAppender(final LogEvent event) {
121 try {
122 primary.callAppender(event);
123 nextCheckNanos = 0;
124 } catch (final Exception ex) {
125 nextCheckNanos = System.nanoTime() + intervalNanos;
126 failover(event, ex);
127 }
128 }
129
130 private void failover(final LogEvent event, final Exception ex) {
131 final RuntimeException re = ex != null ?
132 (ex instanceof LoggingException ? (LoggingException) ex : new LoggingException(ex)) : null;
133 boolean written = false;
134 Exception failoverException = null;
135 for (final AppenderControl control : failoverAppenders) {
136 try {
137 control.callAppender(event);
138 written = true;
139 break;
140 } catch (final Exception fex) {
141 if (failoverException == null) {
142 failoverException = fex;
143 }
144 }
145 }
146 if (!written && !ignoreExceptions()) {
147 if (re != null) {
148 throw re;
149 }
150 throw new LoggingException("Unable to write to failover appenders", failoverException);
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(
184 @PluginAttribute("name") final String name,
185 @PluginAttribute("primary") final String primary,
186 @PluginElement("Failovers") final String[] failovers,
187 @PluginAliases("retryInterval")
188 @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds,
189 @PluginConfiguration final Configuration config,
190 @PluginElement("Filter") final Filter filter,
191 @PluginAttribute("ignoreExceptions") final String ignore) {
192 if (name == null) {
193 LOGGER.error("A name for the Appender must be specified");
194 return null;
195 }
196 if (primary == null) {
197 LOGGER.error("A primary Appender must be specified");
198 return null;
199 }
200 if (failovers == null || failovers.length == 0) {
201 LOGGER.error("At least one failover Appender must be specified");
202 return null;
203 }
204
205 final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS);
206 int retryIntervalMillis;
207 if (seconds >= 0) {
208 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS;
209 } else {
210 LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default");
211 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS;
212 }
213
214 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
215
216 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions);
217 }
218 }