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    /**
119     * Decide if we're going to log <code>event</code> based on whether the
120     * maximum burst of log statements has been exceeded.
121     *
122     * @param level The log level.
123     * @return The onMatch value if the filter passes, onMismatch otherwise.
124     */
125    private Result filter(final Level level) {
126        if (this.level.isMoreSpecificThan(level)) {
127            LogDelay delay = history.poll();
128            while (delay != null) {
129                available.add(delay);
130                delay = history.poll();
131            }
132            delay = available.poll();
133            if (delay != null) {
134                delay.setDelay(burstInterval);
135                history.add(delay);
136                return onMatch;
137            }
138            return onMismatch;
139        }
140        return onMatch;
141
142    }
143
144    /**
145     * Returns the number of available slots. Used for unit testing.
146     * @return The number of available slots.
147     */
148    public int getAvailable() {
149        return available.size();
150    }
151
152    /**
153     * Clear the history. Used for unit testing.
154     */
155    public void clear() {
156        for (LogDelay delay : history) {
157            history.remove(delay);
158            available.add(delay);
159        }
160    }
161
162    @Override
163    public String toString() {
164        return "level=" + level.toString() + ", interval=" + burstInterval + ", max=" + history.size();
165    }
166
167    /**
168     * Delay object to represent each log event that has occurred within the timespan.
169     * 
170     * Consider this class private, package visibility for testing.
171     */
172    private static class LogDelay implements Delayed {
173
174        LogDelay(final long expireTime) {
175            this.expireTime = expireTime;
176        }
177
178        private long expireTime;
179
180        public void setDelay(final long delay) {
181            this.expireTime = delay + System.nanoTime();
182        }
183
184        @Override
185        public long getDelay(final TimeUnit timeUnit) {
186            return timeUnit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
187        }
188
189        @Override
190        public int compareTo(final Delayed delayed) {
191            final long diff = this.expireTime - ((LogDelay) delayed).expireTime;
192            return Long.signum(diff);
193        }
194
195        @Override
196        public boolean equals(final Object o) {
197            if (this == o) {
198                return true;
199            }
200            if (o == null || getClass() != o.getClass()) {
201                return false;
202            }
203
204            final LogDelay logDelay = (LogDelay) o;
205
206            if (expireTime != logDelay.expireTime) {
207                return false;
208            }
209
210            return true;
211        }
212
213        @Override
214        public int hashCode() {
215            return (int) (expireTime ^ (expireTime >>> HASH_SHIFT));
216        }
217    }
218
219    @PluginBuilderFactory
220    public static Builder newBuilder() {
221        return new Builder();
222    }
223
224    public static class Builder implements org.apache.logging.log4j.core.util.Builder<BurstFilter> {
225
226        @PluginBuilderAttribute
227        private Level level = Level.WARN;
228
229        @PluginBuilderAttribute
230        private float rate = DEFAULT_RATE;
231
232        @PluginBuilderAttribute
233        private long maxBurst;
234
235        @PluginBuilderAttribute
236        private Result onMatch = Result.NEUTRAL;
237
238        @PluginBuilderAttribute
239        private Result onMismatch = Result.DENY;
240
241        /**
242         * Sets the logging level to use.
243         */
244        public Builder setLevel(final Level level) {
245            this.level = level;
246            return this;
247        }
248
249        /**
250         * Sets the average number of events per second to allow. This must be a positive number.
251         */
252        public Builder setRate(final float rate) {
253            this.rate = rate;
254            return this;
255        }
256
257        /**
258         * Sets the maximum number of events that can occur before events are filtered for exceeding the average rate.
259         * The default is 10 times the rate.
260         */
261        public Builder setMaxBurst(final long maxBurst) {
262            this.maxBurst = maxBurst;
263            return this;
264        }
265
266        /**
267         * Sets the Result to return when the filter matches. Defaults to Result.NEUTRAL.
268         */
269        public Builder setOnMatch(final Result onMatch) {
270            this.onMatch = onMatch;
271            return this;
272        }
273
274        /**
275         * Sets the Result to return when the filter does not match. The default is Result.DENY.
276         */
277        public Builder setOnMismatch(final Result onMismatch) {
278            this.onMismatch = onMismatch;
279            return this;
280        }
281
282        @Override
283        public BurstFilter build() {
284            if (this.rate <= 0) {
285                this.rate = DEFAULT_RATE;
286            }
287            if (this.maxBurst <= 0) {
288                this.maxBurst = (long) (this.rate * DEFAULT_RATE_MULTIPLE);
289            }
290            return new BurstFilter(this.level, this.rate, this.maxBurst, this.onMatch, this.onMismatch);
291        }
292    }
293}