View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
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  
26  import javax.script.Bindings;
27  
28  import org.apache.logging.log4j.core.Appender;
29  import org.apache.logging.log4j.core.Core;
30  import org.apache.logging.log4j.core.Filter;
31  import org.apache.logging.log4j.core.LifeCycle2;
32  import org.apache.logging.log4j.core.LogEvent;
33  import org.apache.logging.log4j.core.appender.AbstractAppender;
34  import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
35  import org.apache.logging.log4j.core.config.AppenderControl;
36  import org.apache.logging.log4j.core.config.Configuration;
37  import org.apache.logging.log4j.core.config.Node;
38  import org.apache.logging.log4j.core.config.plugins.Plugin;
39  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
40  import org.apache.logging.log4j.core.config.plugins.PluginElement;
41  import org.apache.logging.log4j.core.script.AbstractScript;
42  import org.apache.logging.log4j.core.script.ScriptManager;
43  import org.apache.logging.log4j.core.util.Booleans;
44  
45  /**
46   * This Appender "routes" between various Appenders, some of which can be references to
47   * Appenders defined earlier in the configuration while others can be dynamically created
48   * within this Appender as required. Routing is achieved by specifying a pattern on
49   * the Routing appender declaration. The pattern should contain one or more substitution patterns of
50   * the form "$${[key:]token}". The pattern will be resolved each time the Appender is called using
51   * the built in StrSubstitutor and the StrLookup plugin that matches the specified key.
52   */
53  @Plugin(name = "Routing", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
54  public final class RoutingAppender extends AbstractAppender {
55  
56      public static final String STATIC_VARIABLES_KEY = "staticVariables";
57  
58      public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
59              implements org.apache.logging.log4j.core.util.Builder<RoutingAppender> {
60  
61          // Does not work unless the element is called "Script", I wanted "DefaultRounteScript"...
62          @PluginElement("Script")
63          private AbstractScript defaultRouteScript;
64  
65          @PluginElement("Routes")
66          private Routes routes;
67  
68          @PluginElement("RewritePolicy")
69          private RewritePolicy rewritePolicy;
70  
71          @PluginElement("PurgePolicy")
72          private PurgePolicy purgePolicy;
73  
74          @Override
75          public RoutingAppender build() {
76              final String name = getName();
77              if (name == null) {
78                  LOGGER.error("No name defined for this RoutingAppender");
79                  return null;
80              }
81              if (routes == null) {
82                  LOGGER.error("No routes defined for RoutingAppender {}", name);
83                  return null;
84              }
85              return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
86                      getConfiguration(), purgePolicy, defaultRouteScript);
87          }
88  
89          public Routes getRoutes() {
90              return routes;
91          }
92  
93          public AbstractScript getDefaultRouteScript() {
94              return defaultRouteScript;
95          }
96  
97          public RewritePolicy getRewritePolicy() {
98              return rewritePolicy;
99          }
100 
101         public PurgePolicy getPurgePolicy() {
102             return purgePolicy;
103         }
104 
105         public B withRoutes(@SuppressWarnings("hiding") final Routes routes) {
106             this.routes = routes;
107             return asBuilder();
108         }
109 
110         public B withDefaultRouteScript(@SuppressWarnings("hiding") final AbstractScript defaultRouteScript) {
111             this.defaultRouteScript = defaultRouteScript;
112             return asBuilder();
113         }
114 
115         public B withRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) {
116             this.rewritePolicy = rewritePolicy;
117             return asBuilder();
118         }
119 
120         public void withPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) {
121             this.purgePolicy = purgePolicy;
122         }
123 
124     }
125 
126     @PluginBuilderFactory
127     public static <B extends Builder<B>> B newBuilder() {
128         return new Builder<B>().asBuilder();
129     }
130 
131     private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
132 
133     private final Routes routes;
134     private Route defaultRoute;
135     private final Configuration configuration;
136     private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<>();
137     private final RewritePolicy rewritePolicy;
138     private final PurgePolicy purgePolicy;
139     private final AbstractScript defaultRouteScript;
140     private final ConcurrentMap<Object, Object> scriptStaticVariables = new ConcurrentHashMap<>();
141 
142     private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
143             final RewritePolicy rewritePolicy, final Configuration configuration, final PurgePolicy purgePolicy,
144             final AbstractScript defaultRouteScript) {
145         super(name, filter, null, ignoreExceptions);
146         this.routes = routes;
147         this.configuration = configuration;
148         this.rewritePolicy = rewritePolicy;
149         this.purgePolicy = purgePolicy;
150         if (this.purgePolicy != null) {
151             this.purgePolicy.initialize(this);
152         }
153         this.defaultRouteScript = defaultRouteScript;
154         Route defRoute = null;
155         for (final Route route : routes.getRoutes()) {
156             if (route.getKey() == null) {
157                 if (defRoute == null) {
158                     defRoute = route;
159                 } else {
160                     error("Multiple default routes. Route " + route.toString() + " will be ignored");
161                 }
162             }
163         }
164         defaultRoute = defRoute;
165     }
166 
167     @Override
168     public void start() {
169         if (defaultRouteScript != null) {
170             if (configuration == null) {
171                 error("No Configuration defined for RoutingAppender; required for Script element.");
172             } else {
173                 final ScriptManager scriptManager = configuration.getScriptManager();
174                 scriptManager.addScript(defaultRouteScript);
175                 final Bindings bindings = scriptManager.createBindings(defaultRouteScript);
176                 bindings.put(STATIC_VARIABLES_KEY, scriptStaticVariables);
177                 final Object object = scriptManager.execute(defaultRouteScript.getName(), bindings);
178                 final Route route = routes.getRoute(Objects.toString(object, null));
179                 if (route != null) {
180                     defaultRoute = route;
181                 }
182             }
183         }
184         // Register all the static routes.
185         for (final Route route : routes.getRoutes()) {
186             if (route.getAppenderRef() != null) {
187                 final Appender appender = configuration.getAppender(route.getAppenderRef());
188                 if (appender != null) {
189                     final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
190                     appenders.put(key, new AppenderControl(appender, null, null));
191                 } else {
192                     error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
193                 }
194             }
195         }
196         super.start();
197     }
198 
199     @Override
200     public boolean stop(final long timeout, final TimeUnit timeUnit) {
201         setStopping();
202         super.stop(timeout, timeUnit, false);
203         final Map<String, Appender> map = configuration.getAppenders();
204         for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
205             final Appender appender = entry.getValue().getAppender();
206             if (!map.containsKey(appender.getName())) {
207                 if (appender instanceof LifeCycle2) {
208                     ((LifeCycle2) appender).stop(timeout, timeUnit);
209                 } else {
210                     appender.stop();
211                 }
212             }
213         }
214         setStopped();
215         return true;
216     }
217 
218     @Override
219     public void append(LogEvent event) {
220         if (rewritePolicy != null) {
221             event = rewritePolicy.rewrite(event);
222         }
223         final String pattern = routes.getPattern(event, scriptStaticVariables);
224         final String key = pattern != null ? configuration.getStrSubstitutor().replace(event, pattern) : defaultRoute.getKey();
225         final AppenderControl control = getControl(key, event);
226         if (control != null) {
227             control.callAppender(event);
228         }
229 
230         if (purgePolicy != null) {
231             purgePolicy.update(key, event);
232         }
233     }
234 
235     private synchronized AppenderControl getControl(final String key, final LogEvent event) {
236         AppenderControl control = appenders.get(key);
237         if (control != null) {
238             return control;
239         }
240         Route route = null;
241         for (final Route r : routes.getRoutes()) {
242             if (r.getAppenderRef() == null && key.equals(r.getKey())) {
243                 route = r;
244                 break;
245             }
246         }
247         if (route == null) {
248             route = defaultRoute;
249             control = appenders.get(DEFAULT_KEY);
250             if (control != null) {
251                 return control;
252             }
253         }
254         if (route != null) {
255             final Appender app = createAppender(route, event);
256             if (app == null) {
257                 return null;
258             }
259             control = new AppenderControl(app, null, null);
260             appenders.put(key, control);
261         }
262 
263         return control;
264     }
265 
266     private Appender createAppender(final Route route, final LogEvent event) {
267         final Node routeNode = route.getNode();
268         for (final Node node : routeNode.getChildren()) {
269             if (node.getType().getElementName().equals(Appender.ELEMENT_TYPE)) {
270                 final Node appNode = new Node(node);
271                 configuration.createConfiguration(appNode, event);
272                 if (appNode.getObject() instanceof Appender) {
273                     final Appender app = appNode.getObject();
274                     app.start();
275                     return app;
276                 }
277                 error("Unable to create Appender of type " + node.getName());
278                 return null;
279             }
280         }
281         error("No Appender was configured for route " + route.getKey());
282         return null;
283     }
284 
285     public Map<String, AppenderControl> getAppenders() {
286         return Collections.unmodifiableMap(appenders);
287     }
288 
289     /**
290      * Deletes the specified appender.
291      *
292      * @param key The appender's key
293      */
294     public void deleteAppender(final String key) {
295         LOGGER.debug("Deleting route with " + key + " key ");
296         final AppenderControl control = appenders.remove(key);
297         if (null != control) {
298             LOGGER.debug("Stopping route with " + key + " key");
299             control.getAppender().stop();
300         } else {
301             LOGGER.debug("Route with " + key + " key already deleted");
302         }
303     }
304 
305     /**
306      * Creates a RoutingAppender.
307      * @param name The name of the Appender.
308      * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
309      *               they are propagated to the caller.
310      * @param routes The routing definitions.
311      * @param config The Configuration (automatically added by the Configuration).
312      * @param rewritePolicy A RewritePolicy, if any.
313      * @param filter A Filter to restrict events processed by the Appender or null.
314      * @return The RoutingAppender
315      * @deprecated Since 2.7; use {@link #newBuilder()}
316      */
317     @Deprecated
318     public static RoutingAppender createAppender(
319             final String name,
320             final String ignore,
321             final Routes routes,
322             final Configuration config,
323             final RewritePolicy rewritePolicy,
324             final PurgePolicy purgePolicy,
325             final Filter filter) {
326 
327         final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
328         if (name == null) {
329             LOGGER.error("No name provided for RoutingAppender");
330             return null;
331         }
332         if (routes == null) {
333             LOGGER.error("No routes defined for RoutingAppender");
334             return null;
335         }
336         return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null);
337     }
338 
339     public Route getDefaultRoute() {
340         return defaultRoute;
341     }
342 
343     public AbstractScript getDefaultRouteScript() {
344         return defaultRouteScript;
345     }
346 
347     public PurgePolicy getPurgePolicy() {
348         return purgePolicy;
349     }
350 
351     public RewritePolicy getRewritePolicy() {
352         return rewritePolicy;
353     }
354 
355     public Routes getRoutes() {
356         return routes;
357     }
358 
359     public Configuration getConfiguration() {
360         return configuration;
361     }
362 
363     public ConcurrentMap<Object, Object> getScriptStaticVariables() {
364         return scriptStaticVariables;
365     }
366 }