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