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.filter; 018 019 import org.apache.logging.log4j.Level; 020 import org.apache.logging.log4j.Marker; 021 import org.apache.logging.log4j.core.LogEvent; 022 import org.apache.logging.log4j.core.Logger; 023 import org.apache.logging.log4j.core.config.plugins.Plugin; 024 import org.apache.logging.log4j.core.config.plugins.PluginAttr; 025 import org.apache.logging.log4j.core.config.plugins.PluginFactory; 026 import org.apache.logging.log4j.message.Message; 027 028 import java.util.Iterator; 029 import java.util.Locale; 030 import java.util.Queue; 031 import java.util.concurrent.ConcurrentLinkedQueue; 032 import java.util.concurrent.DelayQueue; 033 import java.util.concurrent.Delayed; 034 import java.util.concurrent.TimeUnit; 035 036 /** 037 * The <code>BurstFilter</code> is a logging filter that regulates logging 038 * traffic. Use this filter when you want to control the maximum burst of log 039 * statements that can be sent to an appender. The filter is configured in the 040 * log4j configuration file. For example, the following configuration limits the 041 * number of INFO level (as well as DEBUG and TRACE) log statements that can be sent to the 042 * console to a burst of 100 with an average rate of 16 per second. WARN, ERROR and FATAL messages would continue to 043 * be delivered.<br> 044 * <br> 045 * <p/> 046 * <code> 047 * <Console name="console"><br> 048 * <PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %x %t %m%n"/><br> 049 * <filters><br> 050 * <Burst level="INFO" rate="16" maxBurst="100"/><br> 051 * </filters><br> 052 * </Console><br> 053 * </code><br> 054 */ 055 056 @Plugin(name = "BurstFilter", type = "Core", elementType = "filter", printObject = true) 057 public final class BurstFilter extends AbstractFilter { 058 059 private static final long NANOS_IN_SECONDS = 1000000000; 060 061 private static final int DEFAULT_RATE = 10; 062 063 private static final int DEFAULT_RATE_MULTIPLE = 100; 064 065 private static final int HASH_SHIFT = 32; 066 067 /** 068 * Level of messages to be filtered. Anything at or below this level will be 069 * filtered out if <code>maxBurst</code> has been exceeded. The default is 070 * WARN meaning any messages that are higher than warn will be logged 071 * regardless of the size of a burst. 072 */ 073 private final Level level; 074 075 private final long burstInterval; 076 077 private final DelayQueue<LogDelay> history = new DelayQueue<LogDelay>(); 078 079 private final Queue<LogDelay> available = new ConcurrentLinkedQueue<LogDelay>(); 080 081 private BurstFilter(Level level, float rate, long maxBurst, Result onMatch, Result onMismatch) { 082 super(onMatch, onMismatch); 083 this.level = level; 084 this.burstInterval = (long) (NANOS_IN_SECONDS * (maxBurst / rate)); 085 for (int i = 0; i < maxBurst; ++i) { 086 available.add(new LogDelay()); 087 } 088 } 089 090 @Override 091 public Result filter(Logger logger, Level level, Marker marker, String msg, Object[] params) { 092 return filter(level); 093 } 094 095 @Override 096 public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { 097 return filter(level); 098 } 099 100 @Override 101 public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { 102 return filter(level); 103 } 104 105 @Override 106 public Result filter(LogEvent event) { 107 return filter(event.getLevel()); 108 } 109 110 /** 111 * Decide if we're going to log <code>event</code> based on whether the 112 * maximum burst of log statements has been exceeded. 113 * 114 * @param level The log level. 115 * @return The onMatch value if the filter passes, onMismatch otherwise. 116 */ 117 private Result filter(Level level) { 118 if (this.level.isAtLeastAsSpecificAs(level)) { 119 LogDelay delay = history.poll(); 120 while (delay != null) { 121 available.add(delay); 122 delay = history.poll(); 123 } 124 delay = available.poll(); 125 if (delay != null) { 126 delay.setDelay(burstInterval); 127 history.add(delay); 128 return onMatch; 129 } 130 return onMismatch; 131 } 132 return onMatch; 133 134 } 135 136 /** 137 * Returns the number of available slots. Used for unit testing. 138 * @return The number of available slots. 139 */ 140 public int getAvailable() { 141 return available.size(); 142 } 143 144 /** 145 * Clear the history. Used for unit testing. 146 */ 147 public void clear() { 148 Iterator<LogDelay> iter = history.iterator(); 149 while (iter.hasNext()) { 150 LogDelay delay = iter.next(); 151 history.remove(delay); 152 available.add(delay); 153 } 154 } 155 156 @Override 157 public String toString() { 158 return "level=" + level.toString() + ", interval=" + burstInterval + ", max=" + history.size(); 159 } 160 161 /** 162 * Delay object to represent each log event that has occurred within the timespan. 163 */ 164 private class LogDelay implements Delayed { 165 166 private long expireTime; 167 168 public LogDelay() { 169 } 170 171 public void setDelay(long delay) { 172 this.expireTime = delay + System.nanoTime(); 173 } 174 175 public long getDelay(TimeUnit timeUnit) { 176 return timeUnit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS); 177 } 178 179 public int compareTo(Delayed delayed) { 180 if (this.expireTime < ((LogDelay) delayed).expireTime) { 181 return -1; 182 } else if (this.expireTime > ((LogDelay) delayed).expireTime) { 183 return 1; 184 } 185 return 0; 186 } 187 188 @Override 189 public boolean equals(Object o) { 190 if (this == o) { 191 return true; 192 } 193 if (o == null || getClass() != o.getClass()) { 194 return false; 195 } 196 197 LogDelay logDelay = (LogDelay) o; 198 199 if (expireTime != logDelay.expireTime) { 200 return false; 201 } 202 203 return true; 204 } 205 206 @Override 207 public int hashCode() { 208 return (int) (expireTime ^ (expireTime >>> HASH_SHIFT)); 209 } 210 } 211 212 /** 213 * @param levelName The logging level. 214 * @param rate The average number of events per second to allow. 215 * @param maxBurst The maximum number of events that can occur before events are filtered for exceeding the 216 * average rate. The default is 10 times the rate. 217 * @param match The Result to return when the filter matches. Defaults to Result.NEUTRAL. 218 * @param mismatch The Result to return when the filter does not match. The default is Result.DENY. 219 * @return A BurstFilter. 220 */ 221 @PluginFactory 222 public static BurstFilter createFilter(@PluginAttr("level") String levelName, 223 @PluginAttr("rate") String rate, 224 @PluginAttr("maxBurst") String maxBurst, 225 @PluginAttr("onmatch") String match, 226 @PluginAttr("onmismatch") String mismatch) { 227 Result onMatch = Result.toResult(match, Result.NEUTRAL); 228 Result onMismatch = Result.toResult(mismatch, Result.DENY); 229 Level level = Level.toLevel(levelName, Level.WARN); 230 float eventRate = rate == null ? DEFAULT_RATE : Float.parseFloat(rate); 231 if (eventRate <= 0) { 232 eventRate = DEFAULT_RATE; 233 } 234 long max = maxBurst == null ? (long) (eventRate * DEFAULT_RATE_MULTIPLE) : Long.parseLong(maxBurst); 235 return new BurstFilter(level, eventRate, max, onMatch, onMismatch); 236 } 237 }