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