1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender.routing;
18
19 import java.util.Collections;
20 import java.util.Map;
21 import java.util.Objects;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicInteger;
26
27 import javax.script.Bindings;
28
29 import org.apache.logging.log4j.core.Appender;
30 import org.apache.logging.log4j.core.Core;
31 import org.apache.logging.log4j.core.Filter;
32 import org.apache.logging.log4j.core.LifeCycle2;
33 import org.apache.logging.log4j.core.LogEvent;
34 import org.apache.logging.log4j.core.appender.AbstractAppender;
35 import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
36 import org.apache.logging.log4j.core.config.AppenderControl;
37 import org.apache.logging.log4j.core.config.Configuration;
38 import org.apache.logging.log4j.core.config.Node;
39 import org.apache.logging.log4j.core.config.Property;
40 import org.apache.logging.log4j.core.config.plugins.Plugin;
41 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
42 import org.apache.logging.log4j.core.config.plugins.PluginElement;
43 import org.apache.logging.log4j.core.script.AbstractScript;
44 import org.apache.logging.log4j.core.script.ScriptManager;
45 import org.apache.logging.log4j.core.util.Booleans;
46
47
48
49
50
51
52
53
54
55 @Plugin(name = "Routing", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
56 public final class RoutingAppender extends AbstractAppender {
57
58 public static final String STATIC_VARIABLES_KEY = "staticVariables";
59
60 public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
61 implements org.apache.logging.log4j.core.util.Builder<RoutingAppender> {
62
63
64 @PluginElement("Script")
65 private AbstractScript defaultRouteScript;
66
67 @PluginElement("Routes")
68 private Routes routes;
69
70 @PluginElement("RewritePolicy")
71 private RewritePolicy rewritePolicy;
72
73 @PluginElement("PurgePolicy")
74 private PurgePolicy purgePolicy;
75
76 @Override
77 public RoutingAppender build() {
78 final String name = getName();
79 if (name == null) {
80 LOGGER.error("No name defined for this RoutingAppender");
81 return null;
82 }
83 if (routes == null) {
84 LOGGER.error("No routes defined for RoutingAppender {}", name);
85 return null;
86 }
87 return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
88 getConfiguration(), purgePolicy, defaultRouteScript, getPropertyArray());
89 }
90
91 public Routes getRoutes() {
92 return routes;
93 }
94
95 public AbstractScript getDefaultRouteScript() {
96 return defaultRouteScript;
97 }
98
99 public RewritePolicy getRewritePolicy() {
100 return rewritePolicy;
101 }
102
103 public PurgePolicy getPurgePolicy() {
104 return purgePolicy;
105 }
106
107 public B withRoutes(@SuppressWarnings("hiding") final Routes routes) {
108 this.routes = routes;
109 return asBuilder();
110 }
111
112 public B withDefaultRouteScript(@SuppressWarnings("hiding") final AbstractScript defaultRouteScript) {
113 this.defaultRouteScript = defaultRouteScript;
114 return asBuilder();
115 }
116
117 public B withRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) {
118 this.rewritePolicy = rewritePolicy;
119 return asBuilder();
120 }
121
122 public void withPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) {
123 this.purgePolicy = purgePolicy;
124 }
125
126 }
127
128 @PluginBuilderFactory
129 public static <B extends Builder<B>> B newBuilder() {
130 return new Builder<B>().asBuilder();
131 }
132
133 private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
134
135 private final Routes routes;
136 private Route defaultRoute;
137 private final Configuration configuration;
138 private final ConcurrentMap<String, CreatedRouteAppenderControl> createdAppenders = new ConcurrentHashMap<>();
139 private final Map<String, AppenderControl> createdAppendersUnmodifiableView = Collections.unmodifiableMap(
140 (Map<String, AppenderControl>) (Map<String, ?>) createdAppenders);
141 private final ConcurrentMap<String, RouteAppenderControl> referencedAppenders = new ConcurrentHashMap<>();
142 private final RewritePolicy rewritePolicy;
143 private final PurgePolicy purgePolicy;
144 private final AbstractScript defaultRouteScript;
145 private final ConcurrentMap<Object, Object> scriptStaticVariables = new ConcurrentHashMap<>();
146
147 private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
148 final RewritePolicy rewritePolicy, final Configuration configuration, final PurgePolicy purgePolicy,
149 final AbstractScript defaultRouteScript, final Property[] properties) {
150 super(name, filter, null, ignoreExceptions, properties);
151 this.routes = routes;
152 this.configuration = configuration;
153 this.rewritePolicy = rewritePolicy;
154 this.purgePolicy = purgePolicy;
155 if (this.purgePolicy != null) {
156 this.purgePolicy.initialize(this);
157 }
158 this.defaultRouteScript = defaultRouteScript;
159 Route defRoute = null;
160 for (final Route route : routes.getRoutes()) {
161 if (route.getKey() == null) {
162 if (defRoute == null) {
163 defRoute = route;
164 } else {
165 error("Multiple default routes. Route " + route.toString() + " will be ignored");
166 }
167 }
168 }
169 defaultRoute = defRoute;
170 }
171
172 @Override
173 public void start() {
174 if (defaultRouteScript != null) {
175 if (configuration == null) {
176 error("No Configuration defined for RoutingAppender; required for Script element.");
177 } else {
178 final ScriptManager scriptManager = configuration.getScriptManager();
179 scriptManager.addScript(defaultRouteScript);
180 final Bindings bindings = scriptManager.createBindings(defaultRouteScript);
181 bindings.put(STATIC_VARIABLES_KEY, scriptStaticVariables);
182 final Object object = scriptManager.execute(defaultRouteScript.getName(), bindings);
183 final Route route = routes.getRoute(Objects.toString(object, null));
184 if (route != null) {
185 defaultRoute = route;
186 }
187 }
188 }
189
190 for (final Route route : routes.getRoutes()) {
191 if (route.getAppenderRef() != null) {
192 final Appender appender = configuration.getAppender(route.getAppenderRef());
193 if (appender != null) {
194 final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
195 referencedAppenders.put(key, new ReferencedRouteAppenderControl(appender));
196 } else {
197 error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
198 }
199 }
200 }
201 super.start();
202 }
203
204 @Override
205 public boolean stop(final long timeout, final TimeUnit timeUnit) {
206 setStopping();
207 super.stop(timeout, timeUnit, false);
208
209 for (final Map.Entry<String, CreatedRouteAppenderControl> entry : createdAppenders.entrySet()) {
210 final Appender appender = entry.getValue().getAppender();
211 if (appender instanceof LifeCycle2) {
212 ((LifeCycle2) appender).stop(timeout, timeUnit);
213 } else {
214 appender.stop();
215 }
216 }
217 setStopped();
218 return true;
219 }
220
221 @Override
222 public void append(LogEvent event) {
223 if (rewritePolicy != null) {
224 event = rewritePolicy.rewrite(event);
225 }
226 final String pattern = routes.getPattern(event, scriptStaticVariables);
227 final String key = pattern != null ? configuration.getStrSubstitutor().replace(event, pattern) : defaultRoute.getKey();
228 final RouteAppenderControl control = getControl(key, event);
229 if (control != null) {
230 try {
231 control.callAppender(event);
232 } finally {
233 control.release();
234 }
235 }
236 updatePurgePolicy(key, event);
237 }
238
239 private void updatePurgePolicy(final String key, final LogEvent event) {
240 if (purgePolicy != null
241
242
243 && !referencedAppenders.containsKey(key)) {
244 purgePolicy.update(key, event);
245 }
246 }
247
248 private synchronized RouteAppenderControl getControl(final String key, final LogEvent event) {
249 RouteAppenderControl control = getAppender(key);
250 if (control != null) {
251 control.checkout();
252 return control;
253 }
254 Route route = null;
255 for (final Route r : routes.getRoutes()) {
256 if (r.getAppenderRef() == null && key.equals(r.getKey())) {
257 route = r;
258 break;
259 }
260 }
261 if (route == null) {
262 route = defaultRoute;
263 control = getAppender(DEFAULT_KEY);
264 if (control != null) {
265 control.checkout();
266 return control;
267 }
268 }
269 if (route != null) {
270 final Appender app = createAppender(route, event);
271 if (app == null) {
272 return null;
273 }
274 CreatedRouteAppenderControl created = new CreatedRouteAppenderControl(app);
275 control = created;
276 createdAppenders.put(key, created);
277 }
278
279 if (control != null) {
280 control.checkout();
281 }
282 return control;
283 }
284
285 private RouteAppenderControl getAppender(final String key) {
286 final RouteAppenderControl result = referencedAppenders.get(key);
287 if (result == null) {
288 return createdAppenders.get(key);
289 }
290 return result;
291 }
292
293 private Appender createAppender(final Route route, final LogEvent event) {
294 final Node routeNode = route.getNode();
295 for (final Node node : routeNode.getChildren()) {
296 if (node.getType().getElementName().equals(Appender.ELEMENT_TYPE)) {
297 final Node appNode = new Node(node);
298 configuration.createConfiguration(appNode, event);
299 if (appNode.getObject() instanceof Appender) {
300 final Appender app = appNode.getObject();
301 app.start();
302 return app;
303 }
304 error("Unable to create Appender of type " + node.getName());
305 return null;
306 }
307 }
308 error("No Appender was configured for route " + route.getKey());
309 return null;
310 }
311
312
313
314
315
316 public Map<String, AppenderControl> getAppenders() {
317 return createdAppendersUnmodifiableView;
318 }
319
320
321
322
323
324
325 public void deleteAppender(final String key) {
326 LOGGER.debug("Deleting route with {} key ", key);
327
328 final CreatedRouteAppenderControl control = createdAppenders.remove(key);
329 if (null != control) {
330 LOGGER.debug("Stopping route with {} key", key);
331
332
333 synchronized (this) {
334 control.pendingDeletion = true;
335 }
336
337
338 control.tryStopAppender();
339 } else {
340 if (referencedAppenders.containsKey(key)) {
341 LOGGER.debug("Route {} using an appender reference may not be removed because " +
342 "the appender may be used outside of the RoutingAppender", key);
343 } else {
344 LOGGER.debug("Route with {} key already deleted", key);
345 }
346 }
347 }
348
349
350
351
352
353
354
355
356
357
358
359
360
361 @Deprecated
362 public static RoutingAppender createAppender(
363 final String name,
364 final String ignore,
365 final Routes routes,
366 final Configuration config,
367 final RewritePolicy rewritePolicy,
368 final PurgePolicy purgePolicy,
369 final Filter filter) {
370
371 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
372 if (name == null) {
373 LOGGER.error("No name provided for RoutingAppender");
374 return null;
375 }
376 if (routes == null) {
377 LOGGER.error("No routes defined for RoutingAppender");
378 return null;
379 }
380 return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null, null);
381 }
382
383 public Route getDefaultRoute() {
384 return defaultRoute;
385 }
386
387 public AbstractScript getDefaultRouteScript() {
388 return defaultRouteScript;
389 }
390
391 public PurgePolicy getPurgePolicy() {
392 return purgePolicy;
393 }
394
395 public RewritePolicy getRewritePolicy() {
396 return rewritePolicy;
397 }
398
399 public Routes getRoutes() {
400 return routes;
401 }
402
403 public Configuration getConfiguration() {
404 return configuration;
405 }
406
407 public ConcurrentMap<Object, Object> getScriptStaticVariables() {
408 return scriptStaticVariables;
409 }
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424 private static abstract class RouteAppenderControl extends AppenderControl {
425
426 RouteAppenderControl(Appender appender) {
427 super(appender, null, null);
428 }
429
430 abstract void checkout();
431
432 abstract void release();
433 }
434
435 private static final class CreatedRouteAppenderControl extends RouteAppenderControl {
436
437 private volatile boolean pendingDeletion = false;
438 private final AtomicInteger depth = new AtomicInteger();
439
440 CreatedRouteAppenderControl(Appender appender) {
441 super(appender);
442 }
443
444 @Override
445 void checkout() {
446 if (pendingDeletion) {
447 LOGGER.warn("CreatedRouteAppenderControl.checkout invoked on a " +
448 "RouteAppenderControl that is pending deletion");
449 }
450 depth.incrementAndGet();
451 }
452
453 @Override
454 void release() {
455 depth.decrementAndGet();
456 tryStopAppender();
457 }
458
459 void tryStopAppender() {
460 if (pendingDeletion
461
462
463
464 && depth.compareAndSet(0, -100_000)) {
465 Appender appender = getAppender();
466 LOGGER.debug("Stopping appender {}", appender);
467 appender.stop();
468 }
469 }
470 }
471
472 private static final class ReferencedRouteAppenderControl extends RouteAppenderControl {
473
474 ReferencedRouteAppenderControl(Appender appender) {
475 super(appender);
476 }
477
478 @Override
479 void checkout() {
480
481 }
482
483 @Override
484 void release() {
485
486 }
487 }
488 }