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 long serialVersionUID = 1L; 050 private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT"; 051 private final Routes routes; 052 private final Route defaultRoute; 053 private final Configuration config; 054 private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<>(); 055 private final RewritePolicy rewritePolicy; 056 private final PurgePolicy purgePolicy; 057 058 private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes, 059 final RewritePolicy rewritePolicy, final Configuration config, PurgePolicy purgePolicy) { 060 super(name, filter, null, ignoreExceptions); 061 this.routes = routes; 062 this.config = config; 063 this.rewritePolicy = rewritePolicy; 064 this.purgePolicy = purgePolicy; 065 if(this.purgePolicy != null) { 066 this.purgePolicy.initialize(this); 067 } 068 Route defRoute = null; 069 for (final Route route : routes.getRoutes()) { 070 if (route.getKey() == null) { 071 if (defRoute == null) { 072 defRoute = route; 073 } else { 074 error("Multiple default routes. Route " + route.toString() + " will be ignored"); 075 } 076 } 077 } 078 defaultRoute = defRoute; 079 } 080 081 @Override 082 public void start() { 083 // Register all the static routes. 084 for (final Route route : routes.getRoutes()) { 085 if (route.getAppenderRef() != null) { 086 final Appender appender = config.getAppender(route.getAppenderRef()); 087 if (appender != null) { 088 final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey(); 089 appenders.put(key, new AppenderControl(appender, null, null)); 090 } else { 091 LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored"); 092 } 093 } 094 } 095 super.start(); 096 } 097 098 @Override 099 public void stop() { 100 super.stop(); 101 final Map<String, Appender> map = config.getAppenders(); 102 for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) { 103 final String name = entry.getValue().getAppender().getName(); 104 if (!map.containsKey(name)) { 105 entry.getValue().getAppender().stop(); 106 } 107 } 108 } 109 110 @Override 111 public void append(LogEvent event) { 112 if (rewritePolicy != null) { 113 event = rewritePolicy.rewrite(event); 114 } 115 final String key = config.getStrSubstitutor().replace(event, routes.getPattern()); 116 final AppenderControl control = getControl(key, event); 117 if (control != null) { 118 control.callAppender(event); 119 } 120 121 if(purgePolicy != null) { 122 purgePolicy.update(key, event); 123 } 124 } 125 126 private synchronized AppenderControl getControl(final String key, final LogEvent event) { 127 AppenderControl control = appenders.get(key); 128 if (control != null) { 129 return control; 130 } 131 Route route = null; 132 for (final Route r : routes.getRoutes()) { 133 if (r.getAppenderRef() == null && key.equals(r.getKey())) { 134 route = r; 135 break; 136 } 137 } 138 if (route == null) { 139 route = defaultRoute; 140 control = appenders.get(DEFAULT_KEY); 141 if (control != null) { 142 return control; 143 } 144 } 145 if (route != null) { 146 final Appender app = createAppender(route, event); 147 if (app == null) { 148 return null; 149 } 150 control = new AppenderControl(app, null, null); 151 appenders.put(key, control); 152 } 153 154 return control; 155 } 156 157 private Appender createAppender(final Route route, final LogEvent event) { 158 final Node routeNode = route.getNode(); 159 for (final Node node : routeNode.getChildren()) { 160 if (node.getType().getElementName().equals("appender")) { 161 final Node appNode = new Node(node); 162 config.createConfiguration(appNode, event); 163 if (appNode.getObject() instanceof Appender) { 164 final Appender app = appNode.getObject(); 165 app.start(); 166 return app; 167 } 168 LOGGER.error("Unable to create Appender of type " + node.getName()); 169 return null; 170 } 171 } 172 LOGGER.error("No Appender was configured for route " + route.getKey()); 173 return null; 174 } 175 176 public Map<String, AppenderControl> getAppenders() { 177 return Collections.unmodifiableMap(appenders); 178 } 179 180 /** 181 * Delete specified appender 182 * 183 * @param key The appender's key 184 */ 185 public void deleteAppender(String key) { 186 LOGGER.debug("Stopping route with key" + key); 187 AppenderControl control = appenders.remove(key); 188 control.getAppender().stop(); 189 } 190 191 /** 192 * Create a RoutingAppender. 193 * @param name The name of the Appender. 194 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 195 * they are propagated to the caller. 196 * @param routes The routing definitions. 197 * @param config The Configuration (automatically added by the Configuration). 198 * @param rewritePolicy A RewritePolicy, if any. 199 * @param filter A Filter to restrict events processed by the Appender or null. 200 * @return The RoutingAppender 201 */ 202 @PluginFactory 203 public static RoutingAppender createAppender( 204 @PluginAttribute("name") final String name, 205 @PluginAttribute("ignoreExceptions") final String ignore, 206 @PluginElement("Routes") final Routes routes, 207 @PluginConfiguration final Configuration config, 208 @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy, 209 @PluginElement("PurgePolicy") final PurgePolicy purgePolicy, 210 @PluginElement("Filter") final Filter filter) { 211 212 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 213 if (name == null) { 214 LOGGER.error("No name provided for RoutingAppender"); 215 return null; 216 } 217 if (routes == null) { 218 LOGGER.error("No routes defined for RoutingAppender"); 219 return null; 220 } 221 return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy); 222 } 223}