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