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 java.util.ArrayList; 020 import java.util.List; 021 import java.util.Map; 022 023 import org.apache.logging.log4j.LoggingException; 024 import org.apache.logging.log4j.core.Appender; 025 import org.apache.logging.log4j.core.Filter; 026 import org.apache.logging.log4j.core.LogEvent; 027 import org.apache.logging.log4j.core.config.AppenderControl; 028 import org.apache.logging.log4j.core.config.Configuration; 029 import org.apache.logging.log4j.core.config.plugins.Plugin; 030 import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 031 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 032 import org.apache.logging.log4j.core.config.plugins.PluginElement; 033 import org.apache.logging.log4j.core.config.plugins.PluginFactory; 034 import org.apache.logging.log4j.core.util.Booleans; 035 import org.apache.logging.log4j.core.util.Constants; 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 extends AbstractAppender { 044 045 private static final int DEFAULT_INTERVAL_SECONDS = 60; 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 intervalMillis; 058 059 private volatile long nextCheckMillis = 0; 060 061 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers, 062 final int intervalMillis, final Configuration config, final boolean ignoreExceptions) { 063 super(name, filter, null, ignoreExceptions); 064 this.primaryRef = primary; 065 this.failovers = failovers; 066 this.config = config; 067 this.intervalMillis = intervalMillis; 068 } 069 070 071 @Override 072 public void start() { 073 final Map<String, Appender> map = config.getAppenders(); 074 int errors = 0; 075 if (map.containsKey(primaryRef)) { 076 primary = new AppenderControl(map.get(primaryRef), null, null); 077 } else { 078 LOGGER.error("Unable to locate primary Appender " + primaryRef); 079 ++errors; 080 } 081 for (final String name : failovers) { 082 if (map.containsKey(name)) { 083 failoverAppenders.add(new AppenderControl(map.get(name), null, null)); 084 } else { 085 LOGGER.error("Failover appender " + name + " is not configured"); 086 } 087 } 088 if (failoverAppenders.isEmpty()) { 089 LOGGER.error("No failover appenders are available"); 090 ++errors; 091 } 092 if (errors == 0) { 093 super.start(); 094 } 095 } 096 097 /** 098 * Handle the Log event. 099 * @param event The LogEvent. 100 */ 101 @Override 102 public void append(final LogEvent event) { 103 if (!isStarted()) { 104 error("FailoverAppender " + getName() + " did not start successfully"); 105 return; 106 } 107 long localCheckMillis = nextCheckMillis; 108 if (localCheckMillis == 0 || System.currentTimeMillis() > localCheckMillis) { 109 callAppender(event); 110 } else { 111 failover(event, null); 112 } 113 } 114 115 private void callAppender(final LogEvent event) { 116 try { 117 primary.callAppender(event); 118 nextCheckMillis = 0; 119 } catch (final Exception ex) { 120 nextCheckMillis = System.currentTimeMillis() + intervalMillis; 121 failover(event, ex); 122 } 123 } 124 125 private void failover(final LogEvent event, final Exception ex) { 126 final RuntimeException re = ex != null ? 127 (ex instanceof LoggingException ? (LoggingException)ex : new LoggingException(ex)) : null; 128 boolean written = false; 129 Exception failoverException = null; 130 for (final AppenderControl control : failoverAppenders) { 131 try { 132 control.callAppender(event); 133 written = true; 134 break; 135 } catch (final Exception fex) { 136 if (failoverException == null) { 137 failoverException = fex; 138 } 139 } 140 } 141 if (!written && !ignoreExceptions()) { 142 if (re != null) { 143 throw re; 144 } else { 145 throw new LoggingException("Unable to write to failover appenders", failoverException); 146 } 147 } 148 } 149 150 @Override 151 public String toString() { 152 final StringBuilder sb = new StringBuilder(getName()); 153 sb.append(" primary=").append(primary).append(", failover={"); 154 boolean first = true; 155 for (final String str : failovers) { 156 if (!first) { 157 sb.append(", "); 158 } 159 sb.append(str); 160 first = false; 161 } 162 sb.append('}'); 163 return sb.toString(); 164 } 165 166 /** 167 * Create a Failover Appender. 168 * @param name The name of the Appender (required). 169 * @param primary The name of the primary Appender (required). 170 * @param failovers The name of one or more Appenders to fail over to (at least one is required). 171 * @param retryIntervalString The retry intervalMillis. 172 * @param config The current Configuration (passed by the Configuration when the appender is created). 173 * @param filter A Filter (optional). 174 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 175 * they are propagated to the caller. 176 * @return The FailoverAppender that was created. 177 */ 178 @PluginFactory 179 public static FailoverAppender createAppender( 180 @PluginAttribute("name") final String name, 181 @PluginAttribute("primary") final String primary, 182 @PluginElement("Failovers") final String[] failovers, 183 @PluginAttribute("retryInterval") final String retryIntervalString, 184 @PluginConfiguration final Configuration config, 185 @PluginElement("Filters") final Filter filter, 186 @PluginAttribute("ignoreExceptions") final String ignore) { 187 if (name == null) { 188 LOGGER.error("A name for the Appender must be specified"); 189 return null; 190 } 191 if (primary == null) { 192 LOGGER.error("A primary Appender must be specified"); 193 return null; 194 } 195 if (failovers == null || failovers.length == 0) { 196 LOGGER.error("At least one failover Appender must be specified"); 197 return null; 198 } 199 200 final int seconds = parseInt(retryIntervalString, DEFAULT_INTERVAL_SECONDS); 201 int retryIntervalMillis; 202 if (seconds >= 0) { 203 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS; 204 } else { 205 LOGGER.warn("Interval " + retryIntervalString + " is less than zero. Using default"); 206 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS; 207 } 208 209 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 210 211 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions); 212 } 213 }