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 import org.apache.logging.log4j.core.helpers.Constants; 031 032 import java.io.Serializable; 033 import java.util.ArrayList; 034 import java.util.List; 035 import java.util.Map; 036 037 /** 038 * The FailoverAppender will capture exceptions in an Appender and then route the event 039 * to a different appender. Hopefully it is obvious that the Appenders must be configured 040 * to not suppress exceptions for the FailoverAppender to work. 041 */ 042 @Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true) 043 public final class FailoverAppender<T extends Serializable> extends AbstractAppender<T> { 044 045 private static final int DEFAULT_INTERVAL = 60 * Constants.MILLIS_IN_SECONDS; 046 047 private final String primaryRef; 048 049 private final String[] failovers; 050 051 private final Configuration config; 052 053 private AppenderControl<?> primary; 054 055 private final List<AppenderControl<?>> failoverAppenders = new ArrayList<AppenderControl<?>>(); 056 057 private final long interval; 058 059 private long nextCheck = 0; 060 061 private volatile boolean failure = false; 062 063 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers, 064 final int interval, final Configuration config, final boolean handleExceptions) { 065 super(name, filter, null, handleExceptions); 066 this.primaryRef = primary; 067 this.failovers = failovers; 068 this.config = config; 069 this.interval = interval; 070 } 071 072 073 @Override 074 @SuppressWarnings("unchecked") 075 public void start() { 076 final Map<String, Appender<?>> map = config.getAppenders(); 077 int errors = 0; 078 if (map.containsKey(primaryRef)) { 079 primary = new AppenderControl(map.get(primaryRef), null, null); 080 } else { 081 LOGGER.error("Unable to locate primary Appender " + primaryRef); 082 ++errors; 083 } 084 for (final String name : failovers) { 085 if (map.containsKey(name)) { 086 failoverAppenders.add(new AppenderControl(map.get(name), null, null)); 087 } else { 088 LOGGER.error("Failover appender " + name + " is not configured"); 089 } 090 } 091 if (failoverAppenders.size() == 0) { 092 LOGGER.error("No failover appenders are available"); 093 ++errors; 094 } 095 if (errors == 0) { 096 super.start(); 097 } 098 } 099 100 /** 101 * Handle the Log event. 102 * @param event The LogEvent. 103 */ 104 @Override 105 public void append(final LogEvent event) { 106 if (!isStarted()) { 107 error("FailoverAppender " + getName() + " did not start successfully"); 108 return; 109 } 110 if (!failure) { 111 callAppender(event); 112 } else { 113 final long current = System.currentTimeMillis(); 114 if (current >= nextCheck) { 115 callAppender(event); 116 } else { 117 failover(event, null); 118 } 119 } 120 } 121 122 private void callAppender(final LogEvent event) { 123 try { 124 primary.callAppender(event); 125 } catch (final Exception ex) { 126 nextCheck = System.currentTimeMillis() + interval; 127 failure = true; 128 failover(event, ex); 129 } 130 } 131 132 private void failover(final LogEvent event, final Exception ex) { 133 final RuntimeException re = ex != null ? new LoggingException(ex) : null; 134 boolean written = false; 135 Exception failoverException = null; 136 for (final AppenderControl control : failoverAppenders) { 137 try { 138 control.callAppender(event); 139 written = true; 140 break; 141 } catch (final Exception fex) { 142 if (failoverException == null) { 143 failoverException = fex; 144 } 145 } 146 } 147 if (!written && !isExceptionSuppressed()) { 148 if (re != null) { 149 throw re; 150 } else { 151 throw new LoggingException("Unable to write to failover appenders", failoverException); 152 } 153 } 154 } 155 156 @Override 157 public String toString() { 158 final StringBuilder sb = new StringBuilder(getName()); 159 sb.append(" primary=").append(primary).append(", failover={"); 160 boolean first = true; 161 for (final String str : failovers) { 162 if (!first) { 163 sb.append(", "); 164 } 165 sb.append(str); 166 first = false; 167 } 168 sb.append("}"); 169 return sb.toString(); 170 } 171 172 /** 173 * Create a Failover Appender. 174 * @param name The name of the Appender (required). 175 * @param primary The name of the primary Appender (required). 176 * @param failovers The name of one or more Appenders to fail over to (at least one is required). 177 * @param interval The retry interval. 178 * @param config The current Configuration (passed by the Configuration when the appender is created). 179 * @param filter A Filter (optional). 180 * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise. 181 * The default is "true". 182 * @return The FailoverAppender that was created. 183 */ 184 @PluginFactory 185 public static <S extends Serializable> FailoverAppender<S> createAppender(@PluginAttr("name") final String name, 186 @PluginAttr("primary") final String primary, 187 @PluginElement("failovers") final String[] failovers, 188 @PluginAttr("retryInterval") final String interval, 189 @PluginConfiguration final Configuration config, 190 @PluginElement("filters") final Filter filter, 191 @PluginAttr("suppressExceptions") final String suppress) { 192 if (name == null) { 193 LOGGER.error("A name for the Appender must be specified"); 194 return null; 195 } 196 if (primary == null) { 197 LOGGER.error("A primary Appender must be specified"); 198 return null; 199 } 200 if (failovers == null || failovers.length == 0) { 201 LOGGER.error("At least one failover Appender must be specified"); 202 return null; 203 } 204 205 int retryInterval; 206 if (interval == null) { 207 retryInterval = DEFAULT_INTERVAL; 208 } else { 209 try { 210 final int value = Integer.parseInt(interval); 211 if (value >= 0) { 212 retryInterval = value * Constants.MILLIS_IN_SECONDS; 213 } else { 214 LOGGER.warn("Interval " + interval + " is less than zero. Using default"); 215 retryInterval = DEFAULT_INTERVAL; 216 } 217 } catch (final NumberFormatException nfe) { 218 LOGGER.error("Interval " + interval + " is non-numeric. Using default"); 219 retryInterval = DEFAULT_INTERVAL; 220 } 221 } 222 223 final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress); 224 225 return new FailoverAppender<S>(name, filter, primary, failovers, retryInterval, config, handleExceptions); 226 } 227 }