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 }