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