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) :
228 defaultRoute.getKey() != null ? defaultRoute.getKey() : DEFAULT_KEY;
229 final RouteAppenderControl control = getControl(key, event);
230 if (control != null) {
231 try {
232 control.callAppender(event);
233 } finally {
234 control.release();
235 }
236 }
237 updatePurgePolicy(key, event);
238 }
239
240 private void updatePurgePolicy(final String key, final LogEvent event) {
241 if (purgePolicy != null
242
243
244 && !referencedAppenders.containsKey(key)) {
245 purgePolicy.update(key, event);
246 }
247 }
248
249 private synchronized RouteAppenderControl getControl(final String key, final LogEvent event) {
250 RouteAppenderControl control = getAppender(key);
251 if (control != null) {
252 control.checkout();
253 return control;
254 }
255 Route route = null;
256 for (final Route r : routes.getRoutes()) {
257 if (r.getAppenderRef() == null && key.equals(r.getKey())) {
258 route = r;
259 break;
260 }
261 }
262 if (route == null) {
263 route = defaultRoute;
264 control = getAppender(DEFAULT_KEY);
265 if (control != null) {
266 control.checkout();
267 return control;
268 }
269 }
270 if (route != null) {
271 final Appender app = createAppender(route, event);
272 if (app == null) {
273 return null;
274 }
275 CreatedRouteAppenderControl created = new CreatedRouteAppenderControl(app);
276 control = created;
277 createdAppenders.put(key, created);
278 }
279
280 if (control != null) {
281 control.checkout();
282 }
283 return control;
284 }
285
286 private RouteAppenderControl getAppender(final String key) {
287 final RouteAppenderControl result = referencedAppenders.get(key);
288 if (result == null) {
289 return createdAppenders.get(key);
290 }
291 return result;
292 }
293
294 private Appender createAppender(final Route route, final LogEvent event) {
295 final Node routeNode = route.getNode();
296 for (final Node node : routeNode.getChildren()) {
297 if (node.getType().getElementName().equals(Appender.ELEMENT_TYPE)) {
298 final Node appNode = new Node(node);
299 configuration.createConfiguration(appNode, event);
300 if (appNode.getObject() instanceof Appender) {
301 final Appender app = appNode.getObject();
302 app.start();
303 return app;
304 }
305 error("Unable to create Appender of type " + node.getName());
306 return null;
307 }
308 }
309 error("No Appender was configured for route " + route.getKey());
310 return null;
311 }
312
313
314
315
316
317 public Map<String, AppenderControl> getAppenders() {
318 return createdAppendersUnmodifiableView;
319 }
320
321
322
323
324
325
326 public void deleteAppender(final String key) {
327 LOGGER.debug("Deleting route with {} key ", key);
328
329 final CreatedRouteAppenderControl control = createdAppenders.remove(key);
330 if (null != control) {
331 LOGGER.debug("Stopping route with {} key", key);
332
333
334 synchronized (this) {
335 control.pendingDeletion = true;
336 }
337
338
339 control.tryStopAppender();
340 } else {
341 if (referencedAppenders.containsKey(key)) {
342 LOGGER.debug("Route {} using an appender reference may not be removed because " +
343 "the appender may be used outside of the RoutingAppender", key);
344 } else {
345 LOGGER.debug("Route with {} key already deleted", key);
346 }
347 }
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361
362 @Deprecated
363 public static RoutingAppender createAppender(
364 final String name,
365 final String ignore,
366 final Routes routes,
367 final Configuration config,
368 final RewritePolicy rewritePolicy,
369 final PurgePolicy purgePolicy,
370 final Filter filter) {
371
372 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
373 if (name == null) {
374 LOGGER.error("No name provided for RoutingAppender");
375 return null;
376 }
377 if (routes == null) {
378 LOGGER.error("No routes defined for RoutingAppender");
379 return null;
380 }
381 return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null, null);
382 }
383
384 public Route getDefaultRoute() {
385 return defaultRoute;
386 }
387
388 public AbstractScript getDefaultRouteScript() {
389 return defaultRouteScript;
390 }
391
392 public PurgePolicy getPurgePolicy() {
393 return purgePolicy;
394 }
395
396 public RewritePolicy getRewritePolicy() {
397 return rewritePolicy;
398 }
399
400 public Routes getRoutes() {
401 return routes;
402 }
403
404 public Configuration getConfiguration() {
405 return configuration;
406 }
407
408 public ConcurrentMap<Object, Object> getScriptStaticVariables() {
409 return scriptStaticVariables;
410 }
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425 private static abstract class RouteAppenderControl extends AppenderControl {
426
427 RouteAppenderControl(Appender appender) {
428 super(appender, null, null);
429 }
430
431 abstract void checkout();
432
433 abstract void release();
434 }
435
436 private static final class CreatedRouteAppenderControl extends RouteAppenderControl {
437
438 private volatile boolean pendingDeletion = false;
439 private final AtomicInteger depth = new AtomicInteger();
440
441 CreatedRouteAppenderControl(Appender appender) {
442 super(appender);
443 }
444
445 @Override
446 void checkout() {
447 if (pendingDeletion) {
448 LOGGER.warn("CreatedRouteAppenderControl.checkout invoked on a " +
449 "RouteAppenderControl that is pending deletion");
450 }
451 depth.incrementAndGet();
452 }
453
454 @Override
455 void release() {
456 depth.decrementAndGet();
457 tryStopAppender();
458 }
459
460 void tryStopAppender() {
461 if (pendingDeletion
462
463
464
465 && depth.compareAndSet(0, -100_000)) {
466 Appender appender = getAppender();
467 LOGGER.debug("Stopping appender {}", appender);
468 appender.stop();
469 }
470 }
471 }
472
473 private static final class ReferencedRouteAppenderControl extends RouteAppenderControl {
474
475 ReferencedRouteAppenderControl(Appender appender) {
476 super(appender);
477 }
478
479 @Override
480 void checkout() {
481
482 }
483
484 @Override
485 void release() {
486
487 }
488 }
489 }