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.util.Map; 034 import java.util.concurrent.ConcurrentHashMap; 035 import java.util.concurrent.ConcurrentMap; 036 037 /** 038 * This Appender "routes" between various Appenders, some of which can be references to 039 * Appenders defined earlier in the configuration while others can be dynamically created 040 * within this Appender as required. Routing is achieved by specifying a pattern on 041 * the Routing appender declaration. The pattern should contain one or more substitution patterns of 042 * the form "$${[key:]token}". The pattern will be resolved each time the Appender is called using 043 * the built in StrSubstitutor and the StrLookup plugin that matches the specified key. 044 */ 045 @Plugin(name = "Routing", type = "Core", elementType = "appender", printObject = true) 046 public final class RoutingAppender extends AbstractAppender { 047 private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT"; 048 private final Routes routes; 049 private final Configuration config; 050 private ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<String, AppenderControl>(); 051 private final RewritePolicy rewritePolicy; 052 053 private RoutingAppender(String name, Filter filter, boolean handleException, Routes routes, 054 RewritePolicy rewritePolicy, Configuration config) { 055 super(name, filter, null, handleException); 056 this.routes = routes; 057 this.config = config; 058 this.rewritePolicy = rewritePolicy; 059 } 060 061 @Override 062 public void start() { 063 Map<String, Appender> map = config.getAppenders(); 064 for (Route route : routes.getRoutes()) { 065 if (route.getAppenderRef() != null) { 066 Appender appender = map.get(route.getAppenderRef()); 067 if (appender != null) { 068 String key = route.getKey() == null ? DEFAULT_KEY : route.getKey(); 069 if (appenders.containsKey(key)) { 070 if (DEFAULT_KEY.equals(key)) { 071 LOGGER.error("Multiple default routes. Only the first will be used"); 072 } else { 073 LOGGER.error("Duplicate route " + key + " is ignored"); 074 } 075 } else { 076 appenders.put(key, new AppenderControl(appender, null, null)); 077 } 078 } else { 079 LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored"); 080 } 081 } 082 } 083 super.start(); 084 } 085 086 @Override 087 public void stop() { 088 super.stop(); 089 Map<String, Appender> map = config.getAppenders(); 090 for (Map.Entry<String, AppenderControl> entry : appenders.entrySet()) { 091 String name = entry.getValue().getAppender().getName(); 092 if (!DEFAULT_KEY.equals(entry.getKey()) && !map.containsKey(name)) { 093 entry.getValue().getAppender().stop(); 094 } 095 } 096 } 097 098 public void append(LogEvent event) { 099 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 }