001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core.appender.routing;
018
019 import java.util.Map;
020 import java.util.concurrent.ConcurrentHashMap;
021 import java.util.concurrent.ConcurrentMap;
022
023 import org.apache.logging.log4j.core.Appender;
024 import org.apache.logging.log4j.core.Filter;
025 import org.apache.logging.log4j.core.LogEvent;
026 import org.apache.logging.log4j.core.appender.AbstractAppender;
027 import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
028 import org.apache.logging.log4j.core.config.AppenderControl;
029 import org.apache.logging.log4j.core.config.Configuration;
030 import org.apache.logging.log4j.core.config.Node;
031 import org.apache.logging.log4j.core.config.plugins.Plugin;
032 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
033 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
034 import org.apache.logging.log4j.core.config.plugins.PluginElement;
035 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
036 import org.apache.logging.log4j.core.util.Booleans;
037
038 /**
039 * This Appender "routes" between various Appenders, some of which can be references to
040 * Appenders defined earlier in the configuration while others can be dynamically created
041 * within this Appender as required. Routing is achieved by specifying a pattern on
042 * the Routing appender declaration. The pattern should contain one or more substitution patterns of
043 * the form "$${[key:]token}". The pattern will be resolved each time the Appender is called using
044 * the built in StrSubstitutor and the StrLookup plugin that matches the specified key.
045 */
046 @Plugin(name = "Routing", category = "Core", elementType = "appender", printObject = true)
047 public final class RoutingAppender extends AbstractAppender {
048 private static final long serialVersionUID = 1L;
049 private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
050 private final Routes routes;
051 private final Route defaultRoute;
052 private final Configuration config;
053 private final ConcurrentMap<String, AppenderControl> appenders =
054 new ConcurrentHashMap<String, AppenderControl>();
055 private final RewritePolicy rewritePolicy;
056
057 private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
058 final RewritePolicy rewritePolicy, final Configuration config) {
059 super(name, filter, null, ignoreExceptions);
060 this.routes = routes;
061 this.config = config;
062 this.rewritePolicy = rewritePolicy;
063 Route defRoute = null;
064 for (final Route route : routes.getRoutes()) {
065 if (route.getKey() == null) {
066 if (defRoute == null) {
067 defRoute = route;
068 } else {
069 error("Multiple default routes. Route " + route.toString() + " will be ignored");
070 }
071 }
072 }
073 defaultRoute = defRoute;
074 }
075
076 @Override
077 public void start() {
078 // Register all the static routes.
079 for (final Route route : routes.getRoutes()) {
080 if (route.getAppenderRef() != null) {
081 final Appender appender = config.getAppender(route.getAppenderRef());
082 if (appender != null) {
083 final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
084 appenders.put(key, new AppenderControl(appender, null, null));
085 } else {
086 LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
087 }
088 }
089 }
090 super.start();
091 }
092
093 @Override
094 public void stop() {
095 super.stop();
096 final Map<String, Appender> map = config.getAppenders();
097 for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
098 final String name = entry.getValue().getAppender().getName();
099 if (!map.containsKey(name)) {
100 entry.getValue().getAppender().stop();
101 }
102 }
103 }
104
105 @Override
106 public void append(LogEvent event) {
107 if (rewritePolicy != null) {
108 event = rewritePolicy.rewrite(event);
109 }
110 final String key = config.getStrSubstitutor().replace(event, routes.getPattern());
111 final AppenderControl control = getControl(key, event);
112 if (control != null) {
113 control.callAppender(event);
114 }
115 }
116
117 private synchronized AppenderControl getControl(final String key, final LogEvent event) {
118 AppenderControl control = appenders.get(key);
119 if (control != null) {
120 return control;
121 }
122 Route route = null;
123 for (final Route r : routes.getRoutes()) {
124 if (r.getAppenderRef() == null && key.equals(r.getKey())) {
125 route = r;
126 break;
127 }
128 }
129 if (route == null) {
130 route = defaultRoute;
131 control = appenders.get(DEFAULT_KEY);
132 if (control != null) {
133 return control;
134 }
135 }
136 if (route != null) {
137 final Appender app = createAppender(route, event);
138 if (app == null) {
139 return null;
140 }
141 control = new AppenderControl(app, null, null);
142 appenders.put(key, control);
143 }
144
145 return control;
146 }
147
148 private Appender createAppender(final Route route, final LogEvent event) {
149 final Node routeNode = route.getNode();
150 for (final Node node : routeNode.getChildren()) {
151 if (node.getType().getElementName().equals("appender")) {
152 final Node appNode = new Node(node);
153 config.createConfiguration(appNode, event);
154 if (appNode.getObject() instanceof Appender) {
155 final Appender app = (Appender) appNode.getObject();
156 app.start();
157 return app;
158 }
159 LOGGER.error("Unable to create Appender of type " + node.getName());
160 return null;
161 }
162 }
163 LOGGER.error("No Appender was configured for route " + route.getKey());
164 return null;
165 }
166
167 /**
168 * Create a RoutingAppender.
169 * @param name The name of the Appender.
170 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
171 * they are propagated to the caller.
172 * @param routes The routing definitions.
173 * @param config The Configuration (automatically added by the Configuration).
174 * @param rewritePolicy A RewritePolicy, if any.
175 * @param filter A Filter to restrict events processed by the Appender or null.
176 * @return The RoutingAppender
177 */
178 @PluginFactory
179 public static RoutingAppender createAppender(
180 @PluginAttribute("name") final String name,
181 @PluginAttribute("ignoreExceptions") final String ignore,
182 @PluginElement("Routes") final Routes routes,
183 @PluginConfiguration final Configuration config,
184 @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy,
185 @PluginElement("Filter") final Filter filter) {
186
187 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
188 if (name == null) {
189 LOGGER.error("No name provided for RoutingAppender");
190 return null;
191 }
192 if (routes == null) {
193 LOGGER.error("No routes defined for RoutingAppender");
194 return null;
195 }
196 return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config);
197 }
198 }