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;
018    
019    import org.apache.logging.log4j.LoggingException;
020    import org.apache.logging.log4j.core.Appender;
021    import org.apache.logging.log4j.core.Filter;
022    import org.apache.logging.log4j.core.LogEvent;
023    import org.apache.logging.log4j.core.config.AppenderControl;
024    import org.apache.logging.log4j.core.config.Configuration;
025    import org.apache.logging.log4j.core.config.plugins.Plugin;
026    import org.apache.logging.log4j.core.config.plugins.PluginAttr;
027    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
028    import org.apache.logging.log4j.core.config.plugins.PluginElement;
029    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
030    
031    import java.util.ArrayList;
032    import java.util.List;
033    import java.util.Map;
034    
035    /**
036     * The FailoverAppender will capture exceptions in an Appender and then route the event
037     * to a different appender. Hopefully it is obvious that the Appenders must be configured
038     * to not suppress exceptions for the FailoverAppender to work.
039     */
040    @Plugin(name = "Failover", type = "Core", elementType = "appender", printObject = true)
041    public final class FailoverAppender extends AbstractAppender {
042    
043        private final String primaryRef;
044    
045        private final String[] failovers;
046    
047        private final Configuration config;
048    
049        private AppenderControl primary;
050    
051        private final List<AppenderControl> failoverAppenders = new ArrayList<AppenderControl>();
052    
053        private FailoverAppender(String name, Filter filter, String primary, String[] failovers,
054                                Configuration config, boolean handleExceptions) {
055            super(name, filter, null, handleExceptions);
056            this.primaryRef = primary;
057            this.failovers = failovers;
058            this.config = config;
059        }
060    
061    
062        @Override
063        public void start() {
064            Map<String, Appender> map = config.getAppenders();
065            int errors = 0;
066            if (map.containsKey(primaryRef)) {
067                primary = new AppenderControl(map.get(primaryRef), null, null);
068            } else {
069                LOGGER.error("Unable to locate primary Appender " + primaryRef);
070                ++errors;
071            }
072            for (String name : failovers) {
073                if (map.containsKey(name)) {
074                    failoverAppenders.add(new AppenderControl(map.get(name), null, null));
075                } else {
076                    LOGGER.error("Failover appender " + name + " is not configured");
077                }
078            }
079            if (failoverAppenders.size() == 0) {
080                LOGGER.error("No failover appenders are available");
081                ++errors;
082            }
083            if (errors == 0) {
084                super.start();
085            }
086        }
087    
088        /**
089         * Handle the Log event.
090         * @param event The LogEvent.
091         */
092        public void append(LogEvent event) {
093            RuntimeException re = null;
094            if (!isStarted()) {
095                error("FailoverAppender " + getName() + " did not start successfully");
096                return;
097            }
098            try {
099                primary.callAppender(event);
100            } catch (Exception ex) {
101                re = new LoggingException(ex);
102                boolean written = false;
103                for (AppenderControl control : failoverAppenders) {
104                    try {
105                        control.callAppender(event);
106                        written = true;
107                        break;
108                    } catch (Exception fex) {
109                        continue;
110                    }
111                }
112                if (!written && !isExceptionSuppressed()) {
113                    throw re;
114                }
115            }
116        }
117    
118        @Override
119        public String toString() {
120            StringBuilder sb = new StringBuilder(getName());
121            sb.append(" primary=").append(primary).append(", failover={");
122            boolean first = true;
123            for (String str : failovers) {
124                if (!first) {
125                    sb.append(", ");
126                }
127                sb.append(str);
128                first = false;
129            }
130            sb.append("}");
131            return sb.toString();
132        }
133    
134        /**
135         * Create a Failover Appender.
136         * @param name The name of the Appender (required).
137         * @param primary The name of the primary Appender (required).
138         * @param failovers The name of one or more Appenders to fail over to (at least one is required).
139         * @param config The current Configuration (passed by the Configuration when the appender is created).
140         * @param filter A Filter (optional).
141         * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise.
142         * The default is "true".
143         * @return The FailoverAppender that was created.
144         */
145        @PluginFactory
146        public static FailoverAppender createAppender(@PluginAttr("name") String name,
147                                                      @PluginAttr("primary") String primary,
148                                                      @PluginElement("failovers") String[] failovers,
149                                                      @PluginConfiguration Configuration config,
150                                                      @PluginElement("filters") Filter filter,
151                                                      @PluginAttr("suppressExceptions") String suppress) {
152            if (name == null) {
153                LOGGER.error("A name for the Appender must be specified");
154                return null;
155            }
156            if (primary == null) {
157                LOGGER.error("A primary Appender must be specified");
158                return null;
159            }
160            if (failovers == null || failovers.length == 0) {
161                LOGGER.error("At least one failover Appender must be specified");
162                return null;
163            }
164    
165            boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress);
166    
167            return new FailoverAppender(name, filter, primary, failovers, config, handleExceptions);
168        }
169    }