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.util.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        // Register all the static routes.
078        for (final Route route : routes.getRoutes()) {
079            if (route.getAppenderRef() != null) {
080                final Appender appender = config.getAppender(route.getAppenderRef());
081                if (appender != null) {
082                    final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
083                    appenders.put(key, new AppenderControl(appender, null, null));
084                } else {
085                    LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
086                }
087            }
088        }
089        super.start();
090    }
091
092    @Override
093    public void stop() {
094        super.stop();
095        final Map<String, Appender> map = config.getAppenders();
096        for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
097            final String name = entry.getValue().getAppender().getName();
098            if (!map.containsKey(name)) {
099                entry.getValue().getAppender().stop();
100            }
101        }
102    }
103
104    @Override
105    public void append(LogEvent event) {
106        if (rewritePolicy != null) {
107            event = rewritePolicy.rewrite(event);
108        }
109        final String key = config.getStrSubstitutor().replace(event, routes.getPattern());
110        final AppenderControl control = getControl(key, event);
111        if (control != null) {
112            control.callAppender(event);
113        }
114    }
115
116    private synchronized AppenderControl getControl(final String key, final LogEvent event) {
117        AppenderControl control = appenders.get(key);
118        if (control != null) {
119            return control;
120        }
121        Route route = null;
122        for (final Route r : routes.getRoutes()) {
123            if (r.getAppenderRef() == null && key.equals(r.getKey())) {
124                route = r;
125                break;
126            }
127        }
128        if (route == null) {
129            route = defaultRoute;
130            control = appenders.get(DEFAULT_KEY);
131            if (control != null) {
132                return control;
133            }
134        }
135        if (route != null) {
136            final Appender app = createAppender(route, event);
137            if (app == null) {
138                return null;
139            }
140            control = new AppenderControl(app, null, null);
141            appenders.put(key, control);
142        }
143
144        return control;
145    }
146
147    private Appender createAppender(final Route route, final LogEvent event) {
148        final Node routeNode = route.getNode();
149        for (final Node node : routeNode.getChildren()) {
150            if (node.getType().getElementName().equals("appender")) {
151                final Node appNode = new Node(node);
152                config.createConfiguration(appNode, event);
153                if (appNode.getObject() instanceof Appender) {
154                    final Appender app = (Appender) appNode.getObject();
155                    app.start();
156                    return app;
157                }
158                LOGGER.error("Unable to create Appender of type " + node.getName());
159                return null;
160            }
161        }
162        LOGGER.error("No Appender was configured for route " + route.getKey());
163        return null;
164    }
165
166    /**
167     * Create a RoutingAppender.
168     * @param name The name of the Appender.
169     * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
170     *               they are propagated to the caller.
171     * @param routes The routing definitions.
172     * @param config The Configuration (automatically added by the Configuration).
173     * @param rewritePolicy A RewritePolicy, if any.
174     * @param filter A Filter to restrict events processed by the Appender or null.
175     * @return The RoutingAppender
176     */
177    @PluginFactory
178    public static RoutingAppender createAppender(
179            @PluginAttribute("name") final String name,
180            @PluginAttribute("ignoreExceptions") final String ignore,
181            @PluginElement("Routes") final Routes routes,
182            @PluginConfiguration final Configuration config,
183            @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy,
184            @PluginElement("Filter") final Filter filter) {
185
186        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
187        if (name == null) {
188            LOGGER.error("No name provided for RoutingAppender");
189            return null;
190        }
191        if (routes == null) {
192            LOGGER.error("No routes defined for RoutingAppender");
193            return null;
194        }
195        return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config);
196    }
197}