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 Route defaultRoute;
050        private final Configuration config;
051        private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<String, AppenderControl>();
052        private final RewritePolicy rewritePolicy;
053    
054        private RoutingAppender(final String name, final Filter filter, final boolean handleException, final Routes routes,
055                                final RewritePolicy rewritePolicy, final Configuration config) {
056            super(name, filter, null, handleException);
057            this.routes = routes;
058            this.config = config;
059            this.rewritePolicy = rewritePolicy;
060            Route defRoute = null;
061            for (final Route route : routes.getRoutes()) {
062                if (route.getKey() == null) {
063                    if (defRoute == null) {
064                        defRoute = route;
065                    } else {
066                        error("Multiple default routes. Route " + route.toString() + " will be ignored");
067                    }
068                }
069            }
070            defaultRoute = defRoute;
071        }
072    
073        @Override
074        public void start() {
075            final Map<String, Appender<?>> map = config.getAppenders();
076            // Register all the static routes.
077            for (final Route route : routes.getRoutes()) {
078                if (route.getAppenderRef() != null) {
079                    final Appender appender = map.get(route.getAppenderRef());
080                    if (appender != null) {
081                        final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
082                        appenders.put(key, new AppenderControl(appender, null, null));
083                    } else {
084                        LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
085                    }
086                }
087            }
088            super.start();
089        }
090    
091        @Override
092        public void stop() {
093            super.stop();
094            final Map<String, Appender<?>> map = config.getAppenders();
095            for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
096                final String name = entry.getValue().getAppender().getName();
097                if (!map.containsKey(name)) {
098                    entry.getValue().getAppender().stop();
099                }
100            }
101        }
102    
103        public void append(LogEvent event) {
104            if (rewritePolicy != null) {
105                event = rewritePolicy.rewrite(event);
106            }
107            final String key = config.getSubst().replace(event, routes.getPattern());
108            final AppenderControl control = getControl(key, event);
109            if (control != null) {
110                control.callAppender(event);
111            }
112        }
113    
114        private synchronized AppenderControl getControl(final String key, final LogEvent event) {
115            AppenderControl control = appenders.get(key);
116            if (control != null) {
117                return control;
118            }
119            Route route = null;
120            for (final Route r : routes.getRoutes()) {
121                if (r.getAppenderRef() == null && key.equals(r.getKey())) {
122                    route = r;
123                    break;
124                }
125            }
126            if (route == null) {
127                route = defaultRoute;
128            }
129            if (route != null) {
130                final Appender app = createAppender(route, event);
131                if (app == null) {
132                    return null;
133                }
134                control = new AppenderControl(app, null, null);
135                appenders.put(key, control);
136            }
137    
138            return control;
139        }
140    
141        private Appender createAppender(final Route route, final LogEvent event) {
142            final Node routeNode = route.getNode();
143            for (final Node node : routeNode.getChildren()) {
144                if (node.getType().getElementName().equals("appender")) {
145                    final Node appNode = new Node(node);
146                    config.createConfiguration(appNode, event);
147                    if (appNode.getObject() instanceof Appender) {
148                        final Appender app = (Appender) appNode.getObject();
149                        app.start();
150                        return app;
151                    }
152                    LOGGER.error("Unable to create Appender of type " + node.getName());
153                    return null;
154                }
155            }
156            LOGGER.error("No Appender was configured for route " + route.getKey());
157            return null;
158        }
159    
160        /**
161         * Create a RoutingAppender.
162         * @param name The name of the Appender.
163         * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
164         * The default is "true".
165         * @param routes The routing definitions.
166         * @param config The Configuration (automatically added by the Configuration).
167         * @param rewritePolicy A RewritePolicy, if any.
168         * @param filter A Filter to restrict events processed by the Appender or null.
169         * @return The RoutingAppender
170         */
171        @PluginFactory
172        public static RoutingAppender createAppender(@PluginAttr("name") final String name,
173                                              @PluginAttr("suppressExceptions") final String suppress,
174                                              @PluginElement("routes") final Routes routes,
175                                              @PluginConfiguration final Configuration config,
176                                              @PluginElement("rewritePolicy") final RewritePolicy rewritePolicy,
177                                              @PluginElement("filters") final Filter filter) {
178    
179            final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
180    
181            if (name == null) {
182                LOGGER.error("No name provided for RoutingAppender");
183                return null;
184            }
185            if (routes == null) {
186                LOGGER.error("No routes defined for RoutingAppender");
187                return null;
188            }
189            return new RoutingAppender(name, filter, handleExceptions, routes, rewritePolicy, config);
190        }
191    }