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.Map;
020import java.util.concurrent.ConcurrentHashMap;
021import java.util.concurrent.ConcurrentMap;
022
023import org.apache.logging.log4j.core.Appender;
024import org.apache.logging.log4j.core.Filter;
025import org.apache.logging.log4j.core.LogEvent;
026import org.apache.logging.log4j.core.appender.AbstractAppender;
027import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
028import org.apache.logging.log4j.core.config.AppenderControl;
029import org.apache.logging.log4j.core.config.Configuration;
030import org.apache.logging.log4j.core.config.Node;
031import org.apache.logging.log4j.core.config.plugins.Plugin;
032import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
033import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
034import org.apache.logging.log4j.core.config.plugins.PluginElement;
035import org.apache.logging.log4j.core.config.plugins.PluginFactory;
036import org.apache.logging.log4j.core.helpers.Booleans;
037
038/**
039 * This Appender "routes" between various Appenders, some of which can be references to
040 * Appenders defined earlier in the configuration while others can be dynamically created
041 * within this Appender as required. Routing is achieved by specifying a pattern on
042 * the Routing appender declaration. The pattern should contain one or more substitution patterns of
043 * the form "$${[key:]token}". The pattern will be resolved each time the Appender is called using
044 * the built in StrSubstitutor and the StrLookup plugin that matches the specified key.
045 */
046@Plugin(name = "Routing", category = "Core", elementType = "appender", printObject = true)
047public final class RoutingAppender extends AbstractAppender {
048    private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
049    private final Routes routes;
050    private final Route defaultRoute;
051    private final Configuration config;
052    private final ConcurrentMap<String, AppenderControl> appenders =
053            new ConcurrentHashMap<String, AppenderControl>();
054    private final RewritePolicy rewritePolicy;
055
056    private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
057                            final RewritePolicy rewritePolicy, final Configuration config) {
058        super(name, filter, null, ignoreExceptions);
059        this.routes = routes;
060        this.config = config;
061        this.rewritePolicy = rewritePolicy;
062        Route defRoute = null;
063        for (final Route route : routes.getRoutes()) {
064            if (route.getKey() == null) {
065                if (defRoute == null) {
066                    defRoute = route;
067                } else {
068                    error("Multiple default routes. Route " + route.toString() + " will be ignored");
069                }
070            }
071        }
072        defaultRoute = defRoute;
073    }
074
075    @Override
076    public void start() {
077        final Map<String, Appender> map = config.getAppenders();
078        // Register all the static routes.
079        for (final Route route : routes.getRoutes()) {
080            if (route.getAppenderRef() != null) {
081                final Appender appender = map.get(route.getAppenderRef());
082                if (appender != null) {
083                    final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
084                    appenders.put(key, new AppenderControl(appender, null, null));
085                } else {
086                    LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
087                }
088            }
089        }
090        super.start();
091    }
092
093    @Override
094    public void stop() {
095        super.stop();
096        final Map<String, Appender> map = config.getAppenders();
097        for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
098            final String name = entry.getValue().getAppender().getName();
099            if (!map.containsKey(name)) {
100                entry.getValue().getAppender().stop();
101            }
102        }
103    }
104
105    @Override
106    public void append(LogEvent event) {
107        if (rewritePolicy != null) {
108            event = rewritePolicy.rewrite(event);
109        }
110        final String key = config.getStrSubstitutor().replace(event, routes.getPattern());
111        final AppenderControl control = getControl(key, event);
112        if (control != null) {
113            control.callAppender(event);
114        }
115    }
116
117    private synchronized AppenderControl getControl(final String key, final LogEvent event) {
118        AppenderControl control = appenders.get(key);
119        if (control != null) {
120            return control;
121        }
122        Route route = null;
123        for (final Route r : routes.getRoutes()) {
124            if (r.getAppenderRef() == null && key.equals(r.getKey())) {
125                route = r;
126                break;
127            }
128        }
129        if (route == null) {
130            route = defaultRoute;
131            control = appenders.get(DEFAULT_KEY);
132            if (control != null) {
133                return control;
134            }
135        }
136        if (route != null) {
137            final Appender app = createAppender(route, event);
138            if (app == null) {
139                return null;
140            }
141            control = new AppenderControl(app, null, null);
142            appenders.put(key, control);
143        }
144
145        return control;
146    }
147
148    private Appender createAppender(final Route route, final LogEvent event) {
149        final Node routeNode = route.getNode();
150        for (final Node node : routeNode.getChildren()) {
151            if (node.getType().getElementName().equals("appender")) {
152                final Node appNode = new Node(node);
153                config.createConfiguration(appNode, event);
154                if (appNode.getObject() instanceof Appender) {
155                    final Appender app = (Appender) appNode.getObject();
156                    app.start();
157                    return app;
158                }
159                LOGGER.error("Unable to create Appender of type " + node.getName());
160                return null;
161            }
162        }
163        LOGGER.error("No Appender was configured for route " + route.getKey());
164        return null;
165    }
166
167    /**
168     * Create a RoutingAppender.
169     * @param name The name of the Appender.
170     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
171     *               they are propagated to the caller.
172     * @param routes The routing definitions.
173     * @param config The Configuration (automatically added by the Configuration).
174     * @param rewritePolicy A RewritePolicy, if any.
175     * @param filter A Filter to restrict events processed by the Appender or null.
176     * @return The RoutingAppender
177     */
178    @PluginFactory
179    public static RoutingAppender createAppender(
180            @PluginAttribute("name") final String name,
181            @PluginAttribute("ignoreExceptions") final String ignore,
182            @PluginElement("Routes") final Routes routes,
183            @PluginConfiguration final Configuration config,
184            @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy,
185            @PluginElement("Filters") final Filter filter) {
186
187        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
188        if (name == null) {
189            LOGGER.error("No name provided for RoutingAppender");
190            return null;
191        }
192        if (routes == null) {
193            LOGGER.error("No routes defined for RoutingAppender");
194            return null;
195        }
196        return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config);
197    }
198}