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     */
017    package org.apache.logging.log4j.core.appender.routing;
018    
019    import java.util.Map;
020    import java.util.concurrent.ConcurrentHashMap;
021    import java.util.concurrent.ConcurrentMap;
022    
023    import org.apache.logging.log4j.core.Appender;
024    import org.apache.logging.log4j.core.Filter;
025    import org.apache.logging.log4j.core.LogEvent;
026    import org.apache.logging.log4j.core.appender.AbstractAppender;
027    import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
028    import org.apache.logging.log4j.core.config.AppenderControl;
029    import org.apache.logging.log4j.core.config.Configuration;
030    import org.apache.logging.log4j.core.config.Node;
031    import org.apache.logging.log4j.core.config.plugins.Plugin;
032    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
033    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
034    import org.apache.logging.log4j.core.config.plugins.PluginElement;
035    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
036    import 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)
047    public final class RoutingAppender extends AbstractAppender {
048        private static final long serialVersionUID = 1L;
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 =
054                new ConcurrentHashMap<String, AppenderControl>();
055        private final RewritePolicy rewritePolicy;
056    
057        private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
058                                final RewritePolicy rewritePolicy, final Configuration config) {
059            super(name, filter, null, ignoreExceptions);
060            this.routes = routes;
061            this.config = config;
062            this.rewritePolicy = rewritePolicy;
063            Route defRoute = null;
064            for (final Route route : routes.getRoutes()) {
065                if (route.getKey() == null) {
066                    if (defRoute == null) {
067                        defRoute = route;
068                    } else {
069                        error("Multiple default routes. Route " + route.toString() + " will be ignored");
070                    }
071                }
072            }
073            defaultRoute = defRoute;
074        }
075    
076        @Override
077        public void start() {
078            // Register all the static routes.
079            for (final Route route : routes.getRoutes()) {
080                if (route.getAppenderRef() != null) {
081                    final Appender appender = config.getAppender(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("Filter") 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    }