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