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