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