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}