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.concurrent.ConcurrentHashMap;
22  import java.util.concurrent.ConcurrentMap;
23  
24  import org.apache.logging.log4j.core.Appender;
25  import org.apache.logging.log4j.core.Filter;
26  import org.apache.logging.log4j.core.LogEvent;
27  import org.apache.logging.log4j.core.appender.AbstractAppender;
28  import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
29  import org.apache.logging.log4j.core.config.AppenderControl;
30  import org.apache.logging.log4j.core.config.Configuration;
31  import org.apache.logging.log4j.core.config.Node;
32  import org.apache.logging.log4j.core.config.plugins.Plugin;
33  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
34  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
35  import org.apache.logging.log4j.core.config.plugins.PluginElement;
36  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
37  import org.apache.logging.log4j.core.util.Booleans;
38  
39  /**
40   * This Appender "routes" between various Appenders, some of which can be references to
41   * Appenders defined earlier in the configuration while others can be dynamically created
42   * within this Appender as required. Routing is achieved by specifying a pattern on
43   * the Routing appender declaration. The pattern should contain one or more substitution patterns of
44   * the form "$${[key:]token}". The pattern will be resolved each time the Appender is called using
45   * the built in StrSubstitutor and the StrLookup plugin that matches the specified key.
46   */
47  @Plugin(name = "Routing", category = "Core", elementType = "appender", printObject = true)
48  public final class RoutingAppender extends AbstractAppender {
49      private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
50      private final Routes routes;
51      private final Route defaultRoute;
52      private final Configuration config;
53      private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<>();
54      private final RewritePolicy rewritePolicy;
55  	  private final PurgePolicy purgePolicy;
56  
57      private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
58                              final RewritePolicy rewritePolicy, final Configuration config, final PurgePolicy purgePolicy) {
59          super(name, filter, null, ignoreExceptions);
60          this.routes = routes;
61          this.config = config;
62          this.rewritePolicy = rewritePolicy;
63          this.purgePolicy = purgePolicy;
64          if(this.purgePolicy != null) {
65          	this.purgePolicy.initialize(this);
66          }
67          Route defRoute = null;
68          for (final Route route : routes.getRoutes()) {
69              if (route.getKey() == null) {
70                  if (defRoute == null) {
71                      defRoute = route;
72                  } else {
73                      error("Multiple default routes. Route " + route.toString() + " will be ignored");
74                  }
75              }
76          }
77          defaultRoute = defRoute;
78      }
79  
80      @Override
81      public void start() {
82          // Register all the static routes.
83          for (final Route route : routes.getRoutes()) {
84              if (route.getAppenderRef() != null) {
85                  final Appender appender = config.getAppender(route.getAppenderRef());
86                  if (appender != null) {
87                      final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
88                      appenders.put(key, new AppenderControl(appender, null, null));
89                  } else {
90                      LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
91                  }
92              }
93          }
94          super.start();
95      }
96  
97      @Override
98      public void stop() {
99          super.stop();
100         final Map<String, Appender> map = config.getAppenders();
101         for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
102             final String name = entry.getValue().getAppender().getName();
103             if (!map.containsKey(name)) {
104                 entry.getValue().getAppender().stop();
105             }
106         }
107     }
108 
109     @Override
110     public void append(LogEvent event) {
111         if (rewritePolicy != null) {
112             event = rewritePolicy.rewrite(event);
113         }
114         final String key = config.getStrSubstitutor().replace(event, routes.getPattern());
115         final AppenderControl control = getControl(key, event);
116         if (control != null) {
117             control.callAppender(event);
118         }
119         
120         if(purgePolicy != null) {
121         	purgePolicy.update(key, event);
122         }
123     }
124 
125 	private synchronized AppenderControl getControl(final String key, final LogEvent event) {
126         AppenderControl control = appenders.get(key);
127         if (control != null) {
128             return control;
129         }
130         Route route = null;
131         for (final Route r : routes.getRoutes()) {
132             if (r.getAppenderRef() == null && key.equals(r.getKey())) {
133                 route = r;
134                 break;
135             }
136         }
137         if (route == null) {
138             route = defaultRoute;
139             control = appenders.get(DEFAULT_KEY);
140             if (control != null) {
141                 return control;
142             }
143         }
144         if (route != null) {
145             final Appender app = createAppender(route, event);
146             if (app == null) {
147                 return null;
148             }
149             control = new AppenderControl(app, null, null);
150             appenders.put(key, control);
151         }
152 
153         return control;
154     }
155 
156     private Appender createAppender(final Route route, final LogEvent event) {
157         final Node routeNode = route.getNode();
158         for (final Node node : routeNode.getChildren()) {
159             if (node.getType().getElementName().equals("appender")) {
160                 final Node appNode = new Node(node);
161                 config.createConfiguration(appNode, event);
162                 if (appNode.getObject() instanceof Appender) {
163                     final Appender app = appNode.getObject();
164                     app.start();
165                     return app;
166                 }
167                 LOGGER.error("Unable to create Appender of type " + node.getName());
168                 return null;
169             }
170         }
171         LOGGER.error("No Appender was configured for route " + route.getKey());
172         return null;
173     }
174     
175     public Map<String, AppenderControl> getAppenders() {
176 		return Collections.unmodifiableMap(appenders);
177 	}    
178     
179     /**
180      * Delete specified appender
181      * 
182      * @param key The appender's key
183      */
184     public void deleteAppender(final String key) {
185     	LOGGER.debug("Stopping route with key" + key);
186     	final AppenderControl control = appenders.remove(key);
187     	control.getAppender().stop();
188     }
189 
190     /**
191      * Create a RoutingAppender.
192      * @param name The name of the Appender.
193      * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
194      *               they are propagated to the caller.
195      * @param routes The routing definitions.
196      * @param config The Configuration (automatically added by the Configuration).
197      * @param rewritePolicy A RewritePolicy, if any.
198      * @param filter A Filter to restrict events processed by the Appender or null.
199      * @return The RoutingAppender
200      */
201     @PluginFactory
202     public static RoutingAppender createAppender(
203             @PluginAttribute("name") final String name,
204             @PluginAttribute("ignoreExceptions") final String ignore,
205             @PluginElement("Routes") final Routes routes,
206             @PluginConfiguration final Configuration config,
207             @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy,
208             @PluginElement("PurgePolicy") final PurgePolicy purgePolicy,
209             @PluginElement("Filter") final Filter filter) {
210 
211         final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
212         if (name == null) {
213             LOGGER.error("No name provided for RoutingAppender");
214             return null;
215         }
216         if (routes == null) {
217             LOGGER.error("No routes defined for RoutingAppender");
218             return null;
219         }
220         return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy);
221     }
222 }