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