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    
018    package org.apache.logging.log4j.core.filter;
019    
020    import java.util.Iterator;
021    import java.util.Queue;
022    import java.util.concurrent.ConcurrentLinkedQueue;
023    import java.util.concurrent.DelayQueue;
024    import java.util.concurrent.Delayed;
025    import java.util.concurrent.TimeUnit;
026    
027    import org.apache.logging.log4j.Level;
028    import org.apache.logging.log4j.Marker;
029    import org.apache.logging.log4j.core.Filter;
030    import org.apache.logging.log4j.core.LogEvent;
031    import org.apache.logging.log4j.core.Logger;
032    import org.apache.logging.log4j.core.config.Node;
033    import org.apache.logging.log4j.core.config.plugins.Plugin;
034    import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
035    import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
036    import 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)
058    public 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<LogDelay>();
081    
082        private final Queue<LogDelay> available = new ConcurrentLinkedQueue<LogDelay>();
083    
084        static LogDelay createLogDelay(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(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    }