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
018package org.apache.logging.log4j.core.filter;
019
020import java.util.Queue;
021import java.util.concurrent.ConcurrentLinkedQueue;
022import java.util.concurrent.DelayQueue;
023import java.util.concurrent.Delayed;
024import java.util.concurrent.TimeUnit;
025
026import org.apache.logging.log4j.Level;
027import org.apache.logging.log4j.Marker;
028import org.apache.logging.log4j.core.Filter;
029import org.apache.logging.log4j.core.LogEvent;
030import org.apache.logging.log4j.core.Logger;
031import org.apache.logging.log4j.core.config.Node;
032import org.apache.logging.log4j.core.config.plugins.Plugin;
033import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
034import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
035import org.apache.logging.log4j.message.Message;
036
037/**
038 * The <code>BurstFilter</code> is a logging filter that regulates logging traffic.
039 *
040 * <p>
041 * Use this filter when you want to control the maximum burst of log statements that can be sent to an appender. The
042 * filter is configured in the log4j configuration file. For example, the following configuration limits the number of
043 * INFO level (as well as DEBUG and TRACE) log statements that can be sent to the console to a burst of 100 with an
044 * average rate of 16 per second. WARN, ERROR and FATAL messages would continue to be delivered.
045 * </p>
046 * <code>
047 * &lt;Console name="console"&gt;<br>
048 * &nbsp;&lt;PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %x %t %m%n"/&gt;<br>
049 * &nbsp;&lt;filters&gt;<br>
050 * &nbsp;&nbsp;&lt;Burst level="INFO" rate="16" maxBurst="100"/&gt;<br>
051 * &nbsp;&lt;/filters&gt;<br>
052 * &lt;/Console&gt;<br>
053 * </code><br>
054 */
055
056@Plugin(name = "BurstFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
057public 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<>();
078
079    private final Queue<LogDelay> available = new ConcurrentLinkedQueue<>();
080
081    static LogDelay createLogDelay(final long expireTime) {
082        return new LogDelay(expireTime);
083    }
084
085    private BurstFilter(final Level level, final float rate, final long maxBurst, final Result onMatch,
086                        final Result onMismatch) {
087        super(onMatch, onMismatch);
088        this.level = level;
089        this.burstInterval = (long) (NANOS_IN_SECONDS * (maxBurst / rate));
090        for (int i = 0; i < maxBurst; ++i) {
091            available.add(createLogDelay(0));
092        }
093    }
094
095    @Override
096    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
097                         final Object... params) {
098        return filter(level);
099    }
100
101    @Override
102    public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
103                         final Throwable t) {
104        return filter(level);
105    }
106
107    @Override
108    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
109                         final Throwable t) {
110        return filter(level);
111    }
112
113    @Override
114    public Result filter(final LogEvent event) {
115        return filter(event.getLevel());
116    }
117
118    @Override
119    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
120            final Object p0) {
121        return filter(level);
122    }
123
124    @Override
125    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
126            final Object p0, final Object p1) {
127        return filter(level);
128    }
129
130    @Override
131    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
132            final Object p0, final Object p1, final Object p2) {
133        return filter(level);
134    }
135
136    @Override
137    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
138            final Object p0, final Object p1, final Object p2, final Object p3) {
139        return filter(level);
140    }
141
142    @Override
143    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
144            final Object p0, final Object p1, final Object p2, final Object p3,
145            final Object p4) {
146        return filter(level);
147    }
148
149    @Override
150    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
151            final Object p0, final Object p1, final Object p2, final Object p3,
152            final Object p4, final Object p5) {
153        return filter(level);
154    }
155
156    @Override
157    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
158            final Object p0, final Object p1, final Object p2, final Object p3,
159            final Object p4, final Object p5, final Object p6) {
160        return filter(level);
161    }
162
163    @Override
164    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
165            final Object p0, final Object p1, final Object p2, final Object p3,
166            final Object p4, final Object p5, final Object p6,
167            final Object p7) {
168        return filter(level);
169    }
170
171    @Override
172    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
173            final Object p0, final Object p1, final Object p2, final Object p3,
174            final Object p4, final Object p5, final Object p6,
175            final Object p7, final Object p8) {
176        return filter(level);
177    }
178
179    @Override
180    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
181            final Object p0, final Object p1, final Object p2, final Object p3,
182            final Object p4, final Object p5, final Object p6,
183            final Object p7, final Object p8, final Object p9) {
184        return filter(level);
185    }
186
187    /**
188     * Decide if we're going to log <code>event</code> based on whether the
189     * maximum burst of log statements has been exceeded.
190     *
191     * @param level The log level.
192     * @return The onMatch value if the filter passes, onMismatch otherwise.
193     */
194    private Result filter(final Level level) {
195        if (this.level.isMoreSpecificThan(level)) {
196            LogDelay delay = history.poll();
197            while (delay != null) {
198                available.add(delay);
199                delay = history.poll();
200            }
201            delay = available.poll();
202            if (delay != null) {
203                delay.setDelay(burstInterval);
204                history.add(delay);
205                return onMatch;
206            }
207            return onMismatch;
208        }
209        return onMatch;
210
211    }
212
213    /**
214     * Returns the number of available slots. Used for unit testing.
215     * @return The number of available slots.
216     */
217    public int getAvailable() {
218        return available.size();
219    }
220
221    /**
222     * Clear the history. Used for unit testing.
223     */
224    public void clear() {
225        for (final LogDelay delay : history) {
226            history.remove(delay);
227            available.add(delay);
228        }
229    }
230
231    @Override
232    public String toString() {
233        return "level=" + level.toString() + ", interval=" + burstInterval + ", max=" + history.size();
234    }
235
236    /**
237     * Delay object to represent each log event that has occurred within the timespan.
238     *
239     * Consider this class private, package visibility for testing.
240     */
241    private static class LogDelay implements Delayed {
242
243        LogDelay(final long expireTime) {
244            this.expireTime = expireTime;
245        }
246
247        private long expireTime;
248
249        public void setDelay(final long delay) {
250            this.expireTime = delay + System.nanoTime();
251        }
252
253        @Override
254        public long getDelay(final TimeUnit timeUnit) {
255            return timeUnit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
256        }
257
258        @Override
259        public int compareTo(final Delayed delayed) {
260            final long diff = this.expireTime - ((LogDelay) delayed).expireTime;
261            return Long.signum(diff);
262        }
263
264        @Override
265        public boolean equals(final Object o) {
266            if (this == o) {
267                return true;
268            }
269            if (o == null || getClass() != o.getClass()) {
270                return false;
271            }
272
273            final LogDelay logDelay = (LogDelay) o;
274
275            if (expireTime != logDelay.expireTime) {
276                return false;
277            }
278
279            return true;
280        }
281
282        @Override
283        public int hashCode() {
284            return (int) (expireTime ^ (expireTime >>> HASH_SHIFT));
285        }
286    }
287
288    @PluginBuilderFactory
289    public static Builder newBuilder() {
290        return new Builder();
291    }
292
293    public static class Builder implements org.apache.logging.log4j.core.util.Builder<BurstFilter> {
294
295        @PluginBuilderAttribute
296        private Level level = Level.WARN;
297
298        @PluginBuilderAttribute
299        private float rate = DEFAULT_RATE;
300
301        @PluginBuilderAttribute
302        private long maxBurst;
303
304        @PluginBuilderAttribute
305        private Result onMatch = Result.NEUTRAL;
306
307        @PluginBuilderAttribute
308        private Result onMismatch = Result.DENY;
309
310        /**
311         * Sets the logging level to use.
312         */
313        public Builder setLevel(final Level level) {
314            this.level = level;
315            return this;
316        }
317
318        /**
319         * Sets the average number of events per second to allow. This must be a positive number.
320         */
321        public Builder setRate(final float rate) {
322            this.rate = rate;
323            return this;
324        }
325
326        /**
327         * Sets the maximum number of events that can occur before events are filtered for exceeding the average rate.
328         * The default is 10 times the rate.
329         */
330        public Builder setMaxBurst(final long maxBurst) {
331            this.maxBurst = maxBurst;
332            return this;
333        }
334
335        /**
336         * Sets the Result to return when the filter matches. Defaults to Result.NEUTRAL.
337         */
338        public Builder setOnMatch(final Result onMatch) {
339            this.onMatch = onMatch;
340            return this;
341        }
342
343        /**
344         * Sets the Result to return when the filter does not match. The default is Result.DENY.
345         */
346        public Builder setOnMismatch(final Result onMismatch) {
347            this.onMismatch = onMismatch;
348            return this;
349        }
350
351        @Override
352        public BurstFilter build() {
353            if (this.rate <= 0) {
354                this.rate = DEFAULT_RATE;
355            }
356            if (this.maxBurst <= 0) {
357                this.maxBurst = (long) (this.rate * DEFAULT_RATE_MULTIPLE);
358            }
359            return new BurstFilter(this.level, this.rate, this.maxBurst, this.onMatch, this.onMismatch);
360        }
361    }
362}