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 }