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 long serialVersionUID = 1L;
50      private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
51      private final Routes routes;
52      private final Route defaultRoute;
53      private final Configuration config;
54      private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<>();
55      private final RewritePolicy rewritePolicy;
56  	private final PurgePolicy purgePolicy;
57  
58      private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
59                              final RewritePolicy rewritePolicy, final Configuration config, PurgePolicy purgePolicy) {
60          super(name, filter, null, ignoreExceptions);
61          this.routes = routes;
62          this.config = config;
63          this.rewritePolicy = rewritePolicy;
64          this.purgePolicy = purgePolicy;
65          if(this.purgePolicy != null) {
66          	this.purgePolicy.initialize(this);
67          }
68          Route defRoute = null;
69          for (final Route route : routes.getRoutes()) {
70              if (route.getKey() == null) {
71                  if (defRoute == null) {
72                      defRoute = route;
73                  } else {
74                      error("Multiple default routes. Route " + route.toString() + " will be ignored");
75                  }
76              }
77          }
78          defaultRoute = defRoute;
79      }
80  
81      @Override
82      public void start() {
83          // Register all the static routes.
84          for (final Route route : routes.getRoutes()) {
85              if (route.getAppenderRef() != null) {
86                  final Appender appender = config.getAppender(route.getAppenderRef());
87                  if (appender != null) {
88                      final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
89                      appenders.put(key, new AppenderControl(appender, null, null));
90                  } else {
91                      LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
92                  }
93              }
94          }
95          super.start();
96      }
97  
98      @Override
99      public void stop() {
100         super.stop();
101         final Map<String, Appender> map = config.getAppenders();
102         for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
103             final String name = entry.getValue().getAppender().getName();
104             if (!map.containsKey(name)) {
105                 entry.getValue().getAppender().stop();
106             }
107         }
108     }
109 
110     @Override
111     public void append(LogEvent event) {
112         if (rewritePolicy != null) {
113             event = rewritePolicy.rewrite(event);
114         }
115         final String key = config.getStrSubstitutor().replace(event, routes.getPattern());
116         final AppenderControl control = getControl(key, event);
117         if (control != null) {
118             control.callAppender(event);
119         }
120         
121         if(purgePolicy != null) {
122         	purgePolicy.update(key, event);
123         }
124     }
125 
126 	private synchronized AppenderControl getControl(final String key, final LogEvent event) {
127         AppenderControl control = appenders.get(key);
128         if (control != null) {
129             return control;
130         }
131         Route route = null;
132         for (final Route r : routes.getRoutes()) {
133             if (r.getAppenderRef() == null && key.equals(r.getKey())) {
134                 route = r;
135                 break;
136             }
137         }
138         if (route == null) {
139             route = defaultRoute;
140             control = appenders.get(DEFAULT_KEY);
141             if (control != null) {
142                 return control;
143             }
144         }
145         if (route != null) {
146             final Appender app = createAppender(route, event);
147             if (app == null) {
148                 return null;
149             }
150             control = new AppenderControl(app, null, null);
151             appenders.put(key, control);
152         }
153 
154         return control;
155     }
156 
157     private Appender createAppender(final Route route, final LogEvent event) {
158         final Node routeNode = route.getNode();
159         for (final Node node : routeNode.getChildren()) {
160             if (node.getType().getElementName().equals("appender")) {
161                 final Node appNode = new Node(node);
162                 config.createConfiguration(appNode, event);
163                 if (appNode.getObject() instanceof Appender) {
164                     final Appender app = appNode.getObject();
165                     app.start();
166                     return app;
167                 }
168                 LOGGER.error("Unable to create Appender of type " + node.getName());
169                 return null;
170             }
171         }
172         LOGGER.error("No Appender was configured for route " + route.getKey());
173         return null;
174     }
175     
176     public Map<String, AppenderControl> getAppenders() {
177 		return Collections.unmodifiableMap(appenders);
178 	}    
179     
180     /**
181      * Delete specified appender
182      * 
183      * @param key The appender's key
184      */
185     public void deleteAppender(String key) {
186     	LOGGER.debug("Stopping route with key" + key);
187     	AppenderControl control = appenders.remove(key);
188     	control.getAppender().stop();
189     }
190 
191     /**
192      * Create a RoutingAppender.
193      * @param name The name of the Appender.
194      * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
195      *               they are propagated to the caller.
196      * @param routes The routing definitions.
197      * @param config The Configuration (automatically added by the Configuration).
198      * @param rewritePolicy A RewritePolicy, if any.
199      * @param filter A Filter to restrict events processed by the Appender or null.
200      * @return The RoutingAppender
201      */
202     @PluginFactory
203     public static RoutingAppender createAppender(
204             @PluginAttribute("name") final String name,
205             @PluginAttribute("ignoreExceptions") final String ignore,
206             @PluginElement("Routes") final Routes routes,
207             @PluginConfiguration final Configuration config,
208             @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy,
209             @PluginElement("PurgePolicy") final PurgePolicy purgePolicy,
210             @PluginElement("Filter") final Filter filter) {
211 
212         final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
213         if (name == null) {
214             LOGGER.error("No name provided for RoutingAppender");
215             return null;
216         }
217         if (routes == null) {
218             LOGGER.error("No routes defined for RoutingAppender");
219             return null;
220         }
221         return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy);
222     }
223 }