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.Objects; 022import java.util.concurrent.ConcurrentHashMap; 023import java.util.concurrent.ConcurrentMap; 024import java.util.concurrent.TimeUnit; 025 026import javax.script.Bindings; 027 028import org.apache.logging.log4j.core.Appender; 029import org.apache.logging.log4j.core.Core; 030import org.apache.logging.log4j.core.Filter; 031import org.apache.logging.log4j.core.LifeCycle2; 032import org.apache.logging.log4j.core.LogEvent; 033import org.apache.logging.log4j.core.appender.AbstractAppender; 034import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; 035import org.apache.logging.log4j.core.config.AppenderControl; 036import org.apache.logging.log4j.core.config.Configuration; 037import org.apache.logging.log4j.core.config.Node; 038import org.apache.logging.log4j.core.config.plugins.Plugin; 039import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 040import org.apache.logging.log4j.core.config.plugins.PluginElement; 041import org.apache.logging.log4j.core.script.AbstractScript; 042import org.apache.logging.log4j.core.script.ScriptManager; 043import org.apache.logging.log4j.core.util.Booleans; 044 045/** 046 * This Appender "routes" between various Appenders, some of which can be references to 047 * Appenders defined earlier in the configuration while others can be dynamically created 048 * within this Appender as required. Routing is achieved by specifying a pattern on 049 * the Routing appender declaration. The pattern should contain one or more substitution patterns of 050 * the form "$${[key:]token}". The pattern will be resolved each time the Appender is called using 051 * the built in StrSubstitutor and the StrLookup plugin that matches the specified key. 052 */ 053@Plugin(name = "Routing", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) 054public final class RoutingAppender extends AbstractAppender { 055 056 public static final String STATIC_VARIABLES_KEY = "staticVariables"; 057 058 public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B> 059 implements org.apache.logging.log4j.core.util.Builder<RoutingAppender> { 060 061 // Does not work unless the element is called "Script", I wanted "DefaultRounteScript"... 062 @PluginElement("Script") 063 private AbstractScript defaultRouteScript; 064 065 @PluginElement("Routes") 066 private Routes routes; 067 068 @PluginElement("RewritePolicy") 069 private RewritePolicy rewritePolicy; 070 071 @PluginElement("PurgePolicy") 072 private PurgePolicy purgePolicy; 073 074 @Override 075 public RoutingAppender build() { 076 final String name = getName(); 077 if (name == null) { 078 LOGGER.error("No name defined for this RoutingAppender"); 079 return null; 080 } 081 if (routes == null) { 082 LOGGER.error("No routes defined for RoutingAppender {}", name); 083 return null; 084 } 085 return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy, 086 getConfiguration(), purgePolicy, defaultRouteScript); 087 } 088 089 public Routes getRoutes() { 090 return routes; 091 } 092 093 public AbstractScript getDefaultRouteScript() { 094 return defaultRouteScript; 095 } 096 097 public RewritePolicy getRewritePolicy() { 098 return rewritePolicy; 099 } 100 101 public PurgePolicy getPurgePolicy() { 102 return purgePolicy; 103 } 104 105 public B withRoutes(@SuppressWarnings("hiding") final Routes routes) { 106 this.routes = routes; 107 return asBuilder(); 108 } 109 110 public B withDefaultRouteScript(@SuppressWarnings("hiding") final AbstractScript defaultRouteScript) { 111 this.defaultRouteScript = defaultRouteScript; 112 return asBuilder(); 113 } 114 115 public B withRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) { 116 this.rewritePolicy = rewritePolicy; 117 return asBuilder(); 118 } 119 120 public void withPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) { 121 this.purgePolicy = purgePolicy; 122 } 123 124 } 125 126 @PluginBuilderFactory 127 public static <B extends Builder<B>> B newBuilder() { 128 return new Builder<B>().asBuilder(); 129 } 130 131 private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT"; 132 133 private final Routes routes; 134 private Route defaultRoute; 135 private final Configuration configuration; 136 private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<>(); 137 private final RewritePolicy rewritePolicy; 138 private final PurgePolicy purgePolicy; 139 private final AbstractScript defaultRouteScript; 140 private final ConcurrentMap<Object, Object> scriptStaticVariables = new ConcurrentHashMap<>(); 141 142 private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes, 143 final RewritePolicy rewritePolicy, final Configuration configuration, final PurgePolicy purgePolicy, 144 final AbstractScript defaultRouteScript) { 145 super(name, filter, null, ignoreExceptions); 146 this.routes = routes; 147 this.configuration = configuration; 148 this.rewritePolicy = rewritePolicy; 149 this.purgePolicy = purgePolicy; 150 if (this.purgePolicy != null) { 151 this.purgePolicy.initialize(this); 152 } 153 this.defaultRouteScript = defaultRouteScript; 154 Route defRoute = null; 155 for (final Route route : routes.getRoutes()) { 156 if (route.getKey() == null) { 157 if (defRoute == null) { 158 defRoute = route; 159 } else { 160 error("Multiple default routes. Route " + route.toString() + " will be ignored"); 161 } 162 } 163 } 164 defaultRoute = defRoute; 165 } 166 167 @Override 168 public void start() { 169 if (defaultRouteScript != null) { 170 if (configuration == null) { 171 error("No Configuration defined for RoutingAppender; required for Script element."); 172 } else { 173 final ScriptManager scriptManager = configuration.getScriptManager(); 174 scriptManager.addScript(defaultRouteScript); 175 final Bindings bindings = scriptManager.createBindings(defaultRouteScript); 176 bindings.put(STATIC_VARIABLES_KEY, scriptStaticVariables); 177 final Object object = scriptManager.execute(defaultRouteScript.getName(), bindings); 178 final Route route = routes.getRoute(Objects.toString(object, null)); 179 if (route != null) { 180 defaultRoute = route; 181 } 182 } 183 } 184 // Register all the static routes. 185 for (final Route route : routes.getRoutes()) { 186 if (route.getAppenderRef() != null) { 187 final Appender appender = configuration.getAppender(route.getAppenderRef()); 188 if (appender != null) { 189 final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey(); 190 appenders.put(key, new AppenderControl(appender, null, null)); 191 } else { 192 error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored"); 193 } 194 } 195 } 196 super.start(); 197 } 198 199 @Override 200 public boolean stop(final long timeout, final TimeUnit timeUnit) { 201 setStopping(); 202 super.stop(timeout, timeUnit, false); 203 final Map<String, Appender> map = configuration.getAppenders(); 204 for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) { 205 final Appender appender = entry.getValue().getAppender(); 206 if (!map.containsKey(appender.getName())) { 207 if (appender instanceof LifeCycle2) { 208 ((LifeCycle2) appender).stop(timeout, timeUnit); 209 } else { 210 appender.stop(); 211 } 212 } 213 } 214 setStopped(); 215 return true; 216 } 217 218 @Override 219 public void append(LogEvent event) { 220 if (rewritePolicy != null) { 221 event = rewritePolicy.rewrite(event); 222 } 223 final String pattern = routes.getPattern(event, scriptStaticVariables); 224 final String key = pattern != null ? configuration.getStrSubstitutor().replace(event, pattern) : defaultRoute.getKey(); 225 final AppenderControl control = getControl(key, event); 226 if (control != null) { 227 control.callAppender(event); 228 } 229 230 if (purgePolicy != null) { 231 purgePolicy.update(key, event); 232 } 233 } 234 235 private synchronized AppenderControl getControl(final String key, final LogEvent event) { 236 AppenderControl control = appenders.get(key); 237 if (control != null) { 238 return control; 239 } 240 Route route = null; 241 for (final Route r : routes.getRoutes()) { 242 if (r.getAppenderRef() == null && key.equals(r.getKey())) { 243 route = r; 244 break; 245 } 246 } 247 if (route == null) { 248 route = defaultRoute; 249 control = appenders.get(DEFAULT_KEY); 250 if (control != null) { 251 return control; 252 } 253 } 254 if (route != null) { 255 final Appender app = createAppender(route, event); 256 if (app == null) { 257 return null; 258 } 259 control = new AppenderControl(app, null, null); 260 appenders.put(key, control); 261 } 262 263 return control; 264 } 265 266 private Appender createAppender(final Route route, final LogEvent event) { 267 final Node routeNode = route.getNode(); 268 for (final Node node : routeNode.getChildren()) { 269 if (node.getType().getElementName().equals(Appender.ELEMENT_TYPE)) { 270 final Node appNode = new Node(node); 271 configuration.createConfiguration(appNode, event); 272 if (appNode.getObject() instanceof Appender) { 273 final Appender app = appNode.getObject(); 274 app.start(); 275 return app; 276 } 277 error("Unable to create Appender of type " + node.getName()); 278 return null; 279 } 280 } 281 error("No Appender was configured for route " + route.getKey()); 282 return null; 283 } 284 285 public Map<String, AppenderControl> getAppenders() { 286 return Collections.unmodifiableMap(appenders); 287 } 288 289 /** 290 * Deletes the specified appender. 291 * 292 * @param key The appender's key 293 */ 294 public void deleteAppender(final String key) { 295 LOGGER.debug("Deleting route with " + key + " key "); 296 final AppenderControl control = appenders.remove(key); 297 if (null != control) { 298 LOGGER.debug("Stopping route with " + key + " key"); 299 control.getAppender().stop(); 300 } else { 301 LOGGER.debug("Route with " + key + " key already deleted"); 302 } 303 } 304 305 /** 306 * Creates a RoutingAppender. 307 * @param name The name of the Appender. 308 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 309 * they are propagated to the caller. 310 * @param routes The routing definitions. 311 * @param config The Configuration (automatically added by the Configuration). 312 * @param rewritePolicy A RewritePolicy, if any. 313 * @param filter A Filter to restrict events processed by the Appender or null. 314 * @return The RoutingAppender 315 * @deprecated Since 2.7; use {@link #newBuilder()} 316 */ 317 @Deprecated 318 public static RoutingAppender createAppender( 319 final String name, 320 final String ignore, 321 final Routes routes, 322 final Configuration config, 323 final RewritePolicy rewritePolicy, 324 final PurgePolicy purgePolicy, 325 final Filter filter) { 326 327 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 328 if (name == null) { 329 LOGGER.error("No name provided for RoutingAppender"); 330 return null; 331 } 332 if (routes == null) { 333 LOGGER.error("No routes defined for RoutingAppender"); 334 return null; 335 } 336 return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null); 337 } 338 339 public Route getDefaultRoute() { 340 return defaultRoute; 341 } 342 343 public AbstractScript getDefaultRouteScript() { 344 return defaultRouteScript; 345 } 346 347 public PurgePolicy getPurgePolicy() { 348 return purgePolicy; 349 } 350 351 public RewritePolicy getRewritePolicy() { 352 return rewritePolicy; 353 } 354 355 public Routes getRoutes() { 356 return routes; 357 } 358 359 public Configuration getConfiguration() { 360 return configuration; 361 } 362 363 public ConcurrentMap<Object, Object> getScriptStaticVariables() { 364 return scriptStaticVariables; 365 } 366}