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 */ 017package org.apache.logging.log4j.core.appender.routing; 018 019import java.util.Collections; 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022import java.util.concurrent.ConcurrentMap; 023 024import org.apache.logging.log4j.core.Appender; 025import org.apache.logging.log4j.core.Filter; 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.appender.AbstractAppender; 028import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; 029import org.apache.logging.log4j.core.config.AppenderControl; 030import org.apache.logging.log4j.core.config.Configuration; 031import org.apache.logging.log4j.core.config.Node; 032import org.apache.logging.log4j.core.config.plugins.Plugin; 033import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 034import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 035import org.apache.logging.log4j.core.config.plugins.PluginElement; 036import org.apache.logging.log4j.core.config.plugins.PluginFactory; 037import org.apache.logging.log4j.core.util.Booleans; 038 039/** 040 * This Appender "routes" between various Appenders, some of which can be references to 041 * Appenders defined earlier in the configuration while others can be dynamically created 042 * within this Appender as required. Routing is achieved by specifying a pattern on 043 * the Routing appender declaration. The pattern should contain one or more substitution patterns of 044 * the form "$${[key:]token}". The pattern will be resolved each time the Appender is called using 045 * the built in StrSubstitutor and the StrLookup plugin that matches the specified key. 046 */ 047@Plugin(name = "Routing", category = "Core", elementType = "appender", printObject = true) 048public final class RoutingAppender extends AbstractAppender { 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 = new ConcurrentHashMap<>(); 054 private final RewritePolicy rewritePolicy; 055 private final PurgePolicy purgePolicy; 056 057 private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes, 058 final RewritePolicy rewritePolicy, final Configuration config, final PurgePolicy purgePolicy) { 059 super(name, filter, null, ignoreExceptions); 060 this.routes = routes; 061 this.config = config; 062 this.rewritePolicy = rewritePolicy; 063 this.purgePolicy = purgePolicy; 064 if(this.purgePolicy != null) { 065 this.purgePolicy.initialize(this); 066 } 067 Route defRoute = null; 068 for (final Route route : routes.getRoutes()) { 069 if (route.getKey() == null) { 070 if (defRoute == null) { 071 defRoute = route; 072 } else { 073 error("Multiple default routes. Route " + route.toString() + " will be ignored"); 074 } 075 } 076 } 077 defaultRoute = defRoute; 078 } 079 080 @Override 081 public void start() { 082 // Register all the static routes. 083 for (final Route route : routes.getRoutes()) { 084 if (route.getAppenderRef() != null) { 085 final Appender appender = config.getAppender(route.getAppenderRef()); 086 if (appender != null) { 087 final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey(); 088 appenders.put(key, new AppenderControl(appender, null, null)); 089 } else { 090 LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored"); 091 } 092 } 093 } 094 super.start(); 095 } 096 097 @Override 098 public void stop() { 099 super.stop(); 100 final Map<String, Appender> map = config.getAppenders(); 101 for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) { 102 final String name = entry.getValue().getAppender().getName(); 103 if (!map.containsKey(name)) { 104 entry.getValue().getAppender().stop(); 105 } 106 } 107 } 108 109 @Override 110 public void append(LogEvent event) { 111 if (rewritePolicy != null) { 112 event = rewritePolicy.rewrite(event); 113 } 114 final String key = config.getStrSubstitutor().replace(event, routes.getPattern()); 115 final AppenderControl control = getControl(key, event); 116 if (control != null) { 117 control.callAppender(event); 118 } 119 120 if(purgePolicy != null) { 121 purgePolicy.update(key, event); 122 } 123 } 124 125 private synchronized AppenderControl getControl(final String key, final LogEvent event) { 126 AppenderControl control = appenders.get(key); 127 if (control != null) { 128 return control; 129 } 130 Route route = null; 131 for (final Route r : routes.getRoutes()) { 132 if (r.getAppenderRef() == null && key.equals(r.getKey())) { 133 route = r; 134 break; 135 } 136 } 137 if (route == null) { 138 route = defaultRoute; 139 control = appenders.get(DEFAULT_KEY); 140 if (control != null) { 141 return control; 142 } 143 } 144 if (route != null) { 145 final Appender app = createAppender(route, event); 146 if (app == null) { 147 return null; 148 } 149 control = new AppenderControl(app, null, null); 150 appenders.put(key, control); 151 } 152 153 return control; 154 } 155 156 private Appender createAppender(final Route route, final LogEvent event) { 157 final Node routeNode = route.getNode(); 158 for (final Node node : routeNode.getChildren()) { 159 if (node.getType().getElementName().equals("appender")) { 160 final Node appNode = new Node(node); 161 config.createConfiguration(appNode, event); 162 if (appNode.getObject() instanceof Appender) { 163 final Appender app = appNode.getObject(); 164 app.start(); 165 return app; 166 } 167 LOGGER.error("Unable to create Appender of type " + node.getName()); 168 return null; 169 } 170 } 171 LOGGER.error("No Appender was configured for route " + route.getKey()); 172 return null; 173 } 174 175 public Map<String, AppenderControl> getAppenders() { 176 return Collections.unmodifiableMap(appenders); 177 } 178 179 /** 180 * Delete specified appender 181 * 182 * @param key The appender's key 183 */ 184 public void deleteAppender(final String key) { 185 LOGGER.debug("Stopping route with key" + key); 186 final AppenderControl control = appenders.remove(key); 187 control.getAppender().stop(); 188 } 189 190 /** 191 * Create a RoutingAppender. 192 * @param name The name of the Appender. 193 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 194 * they are propagated to the caller. 195 * @param routes The routing definitions. 196 * @param config The Configuration (automatically added by the Configuration). 197 * @param rewritePolicy A RewritePolicy, if any. 198 * @param filter A Filter to restrict events processed by the Appender or null. 199 * @return The RoutingAppender 200 */ 201 @PluginFactory 202 public static RoutingAppender createAppender( 203 @PluginAttribute("name") final String name, 204 @PluginAttribute("ignoreExceptions") final String ignore, 205 @PluginElement("Routes") final Routes routes, 206 @PluginConfiguration final Configuration config, 207 @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy, 208 @PluginElement("PurgePolicy") final PurgePolicy purgePolicy, 209 @PluginElement("Filter") final Filter filter) { 210 211 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 212 if (name == null) { 213 LOGGER.error("No name provided for RoutingAppender"); 214 return null; 215 } 216 if (routes == null) { 217 LOGGER.error("No routes defined for RoutingAppender"); 218 return null; 219 } 220 return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy); 221 } 222}