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 public long getDelay(final TimeUnit timeUnit) {
179 return timeUnit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
180 }
181
182 public int compareTo(final Delayed delayed) {
183 if (this.expireTime < ((LogDelay) delayed).expireTime) {
184 return -1;
185 } else if (this.expireTime > ((LogDelay) delayed).expireTime) {
186 return 1;
187 }
188 return 0;
189 }
190
191 @Override
192 public boolean equals(final Object o) {
193 if (this == o) {
194 return true;
195 }
196 if (o == null || getClass() != o.getClass()) {
197 return false;
198 }
199
200 final LogDelay logDelay = (LogDelay) o;
201
202 if (expireTime != logDelay.expireTime) {
203 return false;
204 }
205
206 return true;
207 }
208
209 @Override
210 public int hashCode() {
211 return (int) (expireTime ^ (expireTime >>> HASH_SHIFT));
212 }
213 }
214
215 /**
216 * @param levelName The logging level.
217 * @param rate The average number of events per second to allow.
218 * @param maxBurst The maximum number of events that can occur before events are filtered for exceeding the
219 * average rate. The default is 10 times the rate.
220 * @param match The Result to return when the filter matches. Defaults to Result.NEUTRAL.
221 * @param mismatch The Result to return when the filter does not match. The default is Result.DENY.
222 * @return A BurstFilter.
223 */
224 @PluginFactory
225 public static BurstFilter createFilter(@PluginAttr("level") final String levelName,
226 @PluginAttr("rate") final String rate,
227 @PluginAttr("maxBurst") final String maxBurst,
228 @PluginAttr("onmatch") final String match,
229 @PluginAttr("onmismatch") final String mismatch) {
230 final Result onMatch = Result.toResult(match, Result.NEUTRAL);
231 final Result onMismatch = Result.toResult(mismatch, Result.DENY);
232 final Level level = Level.toLevel(levelName, Level.WARN);
233 float eventRate = rate == null ? DEFAULT_RATE : Float.parseFloat(rate);
234 if (eventRate <= 0) {
235 eventRate = DEFAULT_RATE;
236 }
237 final long max = maxBurst == null ? (long) (eventRate * DEFAULT_RATE_MULTIPLE) : Long.parseLong(maxBurst);
238 return new BurstFilter(level, eventRate, max, onMatch, onMismatch);
239 }
240 }