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.filter;
018
019import java.util.Iterator;
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.LogEvent;
029import org.apache.logging.log4j.core.Logger;
030import org.apache.logging.log4j.core.config.plugins.Plugin;
031import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginFactory;
033import org.apache.logging.log4j.message.Message;
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 * &lt;Console name="console"&gt;<br>
047 * &nbsp;&lt;PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %x %t %m%n"/&gt;<br>
048 * &nbsp;&lt;filters&gt;<br>
049 * &nbsp;&nbsp;&lt;Burst level="INFO" rate="16" maxBurst="100"/&gt;<br>
050 * &nbsp;&lt;/filters&gt;<br>
051 * &lt;/Console&gt;<br>
052 * </code><br>
053 */
054
055@Plugin(name = "BurstFilter", category = "Core", elementType = "filter", printObject = true)
056public 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.isMoreSpecificThan(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 level  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(
228            @PluginAttribute("level") final Level level,
229            @PluginAttribute("rate") final Float rate,
230            @PluginAttribute("maxBurst") final Long maxBurst,
231            @PluginAttribute("onMatch") final Result match,
232            @PluginAttribute("onMismatch") final Result mismatch) {
233        final Result onMatch = match == null ? Result.NEUTRAL : match;
234        final Result onMismatch = mismatch == null ? Result.DENY : mismatch;
235        final Level actualLevel = level == null ? Level.WARN : level;
236        float eventRate = rate == null ? DEFAULT_RATE : rate;
237        if (eventRate <= 0) {
238            eventRate = DEFAULT_RATE;
239        }
240        final long max = maxBurst == null ? (long) (eventRate * DEFAULT_RATE_MULTIPLE) : maxBurst;
241        return new BurstFilter(actualLevel, eventRate, max, onMatch, onMismatch);
242    }
243}