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}