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