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