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 org.apache.logging.log4j.core.Appender;
020 import org.apache.logging.log4j.core.Filter;
021 import org.apache.logging.log4j.core.LogEvent;
022 import org.apache.logging.log4j.core.appender.AbstractAppender;
023 import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
024 import org.apache.logging.log4j.core.config.AppenderControl;
025 import org.apache.logging.log4j.core.config.Configuration;
026 import org.apache.logging.log4j.core.config.Node;
027 import org.apache.logging.log4j.core.config.plugins.Plugin;
028 import org.apache.logging.log4j.core.config.plugins.PluginAttr;
029 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
030 import org.apache.logging.log4j.core.config.plugins.PluginElement;
031 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
032
033 import java.io.Serializable;
034 import java.util.Map;
035 import java.util.concurrent.ConcurrentHashMap;
036 import java.util.concurrent.ConcurrentMap;
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<T extends Serializable> extends AbstractAppender<T> {
048 private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
049 private final Routes routes;
050 private final Route defaultRoute;
051 private final Configuration config;
052 private final ConcurrentMap<String, AppenderControl<T>> appenders =
053 new ConcurrentHashMap<String, AppenderControl<T>>();
054 private final RewritePolicy rewritePolicy;
055
056 private RoutingAppender(final String name, final Filter filter, final boolean handleException, final Routes routes,
057 final RewritePolicy rewritePolicy, final Configuration config) {
058 super(name, filter, null, handleException);
059 this.routes = routes;
060 this.config = config;
061 this.rewritePolicy = rewritePolicy;
062 Route defRoute = null;
063 for (final Route route : routes.getRoutes()) {
064 if (route.getKey() == null) {
065 if (defRoute == null) {
066 defRoute = route;
067 } else {
068 error("Multiple default routes. Route " + route.toString() + " will be ignored");
069 }
070 }
071 }
072 defaultRoute = defRoute;
073 }
074
075 @Override
076 @SuppressWarnings("unchecked")
077 public void start() {
078 final Map<String, Appender<?>> map = config.getAppenders();
079 // Register all the static routes.
080 for (final Route route : routes.getRoutes()) {
081 if (route.getAppenderRef() != null) {
082 final Appender<?> appender = map.get(route.getAppenderRef());
083 if (appender != null) {
084 final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
085 appenders.put(key, new AppenderControl(appender, null, null));
086 } else {
087 LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
088 }
089 }
090 }
091 super.start();
092 }
093
094 @Override
095 public void stop() {
096 super.stop();
097 final Map<String, Appender<?>> map = config.getAppenders();
098 for (final Map.Entry<String, AppenderControl<T>> entry : appenders.entrySet()) {
099 final String name = entry.getValue().getAppender().getName();
100 if (!map.containsKey(name)) {
101 entry.getValue().getAppender().stop();
102 }
103 }
104 }
105
106 @Override
107 public void append(LogEvent event) {
108 if (rewritePolicy != null) {
109 event = rewritePolicy.rewrite(event);
110 }
111 final String key = config.getSubst().replace(event, routes.getPattern());
112 final AppenderControl<T> control = getControl(key, event);
113 if (control != null) {
114 control.callAppender(event);
115 }
116 }
117
118 private synchronized AppenderControl<T> getControl(final String key, final LogEvent event) {
119 AppenderControl<T> control = appenders.get(key);
120 if (control != null) {
121 return control;
122 }
123 Route route = null;
124 for (final Route r : routes.getRoutes()) {
125 if (r.getAppenderRef() == null && key.equals(r.getKey())) {
126 route = r;
127 break;
128 }
129 }
130 if (route == null) {
131 route = defaultRoute;
132 }
133 if (route != null) {
134 final Appender<T> app = createAppender(route, event);
135 if (app == null) {
136 return null;
137 }
138 control = new AppenderControl<T>(app, null, null);
139 appenders.put(key, control);
140 }
141
142 return control;
143 }
144
145 private Appender<T> createAppender(final Route route, final LogEvent event) {
146 final Node routeNode = route.getNode();
147 for (final Node node : routeNode.getChildren()) {
148 if (node.getType().getElementName().equals("appender")) {
149 final Node appNode = new Node(node);
150 config.createConfiguration(appNode, event);
151 if (appNode.getObject() instanceof Appender) {
152 @SuppressWarnings("unchecked")
153 final Appender<T> app = (Appender<T>) appNode.getObject();
154 app.start();
155 return app;
156 }
157 LOGGER.error("Unable to create Appender of type " + node.getName());
158 return null;
159 }
160 }
161 LOGGER.error("No Appender was configured for route " + route.getKey());
162 return null;
163 }
164
165 /**
166 * Create a RoutingAppender.
167 * @param name The name of the Appender.
168 * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
169 * The default is "true".
170 * @param routes The routing definitions.
171 * @param config The Configuration (automatically added by the Configuration).
172 * @param rewritePolicy A RewritePolicy, if any.
173 * @param filter A Filter to restrict events processed by the Appender or null.
174 * @return The RoutingAppender
175 */
176 @PluginFactory
177 public static <S extends Serializable> RoutingAppender<S> createAppender(@PluginAttr("name") final String name,
178 @PluginAttr("suppressExceptions") final String suppress,
179 @PluginElement("routes") final Routes routes,
180 @PluginConfiguration final Configuration config,
181 @PluginElement("rewritePolicy") final RewritePolicy rewritePolicy,
182 @PluginElement("filters") final Filter filter) {
183
184 final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
185
186 if (name == null) {
187 LOGGER.error("No name provided for RoutingAppender");
188 return null;
189 }
190 if (routes == null) {
191 LOGGER.error("No routes defined for RoutingAppender");
192 return null;
193 }
194 return new RoutingAppender<S>(name, filter, handleExceptions, routes, rewritePolicy, config);
195 }
196 }