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 */ 017package org.apache.logging.log4j.core.appender; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Map; 022 023import org.apache.logging.log4j.LoggingException; 024import org.apache.logging.log4j.core.Appender; 025import org.apache.logging.log4j.core.Filter; 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.config.AppenderControl; 028import org.apache.logging.log4j.core.config.Configuration; 029import org.apache.logging.log4j.core.config.plugins.Plugin; 030import org.apache.logging.log4j.core.config.plugins.PluginAliases; 031import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 032import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 033import org.apache.logging.log4j.core.config.plugins.PluginElement; 034import org.apache.logging.log4j.core.config.plugins.PluginFactory; 035import org.apache.logging.log4j.core.util.Booleans; 036import org.apache.logging.log4j.core.util.Constants; 037 038/** 039 * The FailoverAppender will capture exceptions in an Appender and then route the event 040 * to a different appender. Hopefully it is obvious that the Appenders must be configured 041 * to not suppress exceptions for the FailoverAppender to work. 042 */ 043@Plugin(name = "Failover", category = "Core", elementType = "appender", printObject = true) 044public final class FailoverAppender extends AbstractAppender { 045 046 private static final long serialVersionUID = 1L; 047 048 private static final int DEFAULT_INTERVAL_SECONDS = 60; 049 050 private final String primaryRef; 051 052 private final String[] failovers; 053 054 private final Configuration config; 055 056 private AppenderControl primary; 057 058 private final List<AppenderControl> failoverAppenders = new ArrayList<AppenderControl>(); 059 060 private final long intervalMillis; 061 062 private volatile long nextCheckMillis = 0; 063 064 private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers, 065 final int intervalMillis, final Configuration config, final boolean ignoreExceptions) { 066 super(name, filter, null, ignoreExceptions); 067 this.primaryRef = primary; 068 this.failovers = failovers; 069 this.config = config; 070 this.intervalMillis = intervalMillis; 071 } 072 073 074 @Override 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.isEmpty()) { 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 final long localCheckMillis = nextCheckMillis; 111 if (localCheckMillis == 0 || System.currentTimeMillis() > localCheckMillis) { 112 callAppender(event); 113 } else { 114 failover(event, null); 115 } 116 } 117 118 private void callAppender(final LogEvent event) { 119 try { 120 primary.callAppender(event); 121 nextCheckMillis = 0; 122 } catch (final Exception ex) { 123 nextCheckMillis = System.currentTimeMillis() + intervalMillis; 124 failover(event, ex); 125 } 126 } 127 128 private void failover(final LogEvent event, final Exception ex) { 129 final RuntimeException re = ex != null ? 130 (ex instanceof LoggingException ? (LoggingException)ex : new LoggingException(ex)) : null; 131 boolean written = false; 132 Exception failoverException = null; 133 for (final AppenderControl control : failoverAppenders) { 134 try { 135 control.callAppender(event); 136 written = true; 137 break; 138 } catch (final Exception fex) { 139 if (failoverException == null) { 140 failoverException = fex; 141 } 142 } 143 } 144 if (!written && !ignoreExceptions()) { 145 if (re != null) { 146 throw re; 147 } 148 throw new LoggingException("Unable to write to failover appenders", failoverException); 149 } 150 } 151 152 @Override 153 public String toString() { 154 final StringBuilder sb = new StringBuilder(getName()); 155 sb.append(" primary=").append(primary).append(", failover={"); 156 boolean first = true; 157 for (final String str : failovers) { 158 if (!first) { 159 sb.append(", "); 160 } 161 sb.append(str); 162 first = false; 163 } 164 sb.append('}'); 165 return sb.toString(); 166 } 167 168 /** 169 * Create a Failover Appender. 170 * @param name The name of the Appender (required). 171 * @param primary The name of the primary Appender (required). 172 * @param failovers The name of one or more Appenders to fail over to (at least one is required). 173 * @param retryIntervalSeconds The retry interval in seconds. 174 * @param config The current Configuration (passed by the Configuration when the appender is created). 175 * @param filter A Filter (optional). 176 * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise 177 * they are propagated to the caller. 178 * @return The FailoverAppender that was created. 179 */ 180 @PluginFactory 181 public static FailoverAppender createAppender( 182 @PluginAttribute("name") final String name, 183 @PluginAttribute("primary") final String primary, 184 @PluginElement("Failovers") final String[] failovers, 185 @PluginAliases("retryInterval") // deprecated 186 @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds, 187 @PluginConfiguration final Configuration config, 188 @PluginElement("Filter") final Filter filter, 189 @PluginAttribute("ignoreExceptions") final String ignore) { 190 if (name == null) { 191 LOGGER.error("A name for the Appender must be specified"); 192 return null; 193 } 194 if (primary == null) { 195 LOGGER.error("A primary Appender must be specified"); 196 return null; 197 } 198 if (failovers == null || failovers.length == 0) { 199 LOGGER.error("At least one failover Appender must be specified"); 200 return null; 201 } 202 203 final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS); 204 int retryIntervalMillis; 205 if (seconds >= 0) { 206 retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS; 207 } else { 208 LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default"); 209 retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS; 210 } 211 212 final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 213 214 return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions); 215 } 216}