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    package org.apache.logging.log4j.core.filter;
018    
019    import java.util.Iterator;
020    import java.util.Queue;
021    import java.util.concurrent.ConcurrentLinkedQueue;
022    import java.util.concurrent.DelayQueue;
023    import java.util.concurrent.Delayed;
024    import java.util.concurrent.TimeUnit;
025    
026    import org.apache.logging.log4j.Level;
027    import org.apache.logging.log4j.Marker;
028    import org.apache.logging.log4j.core.LogEvent;
029    import org.apache.logging.log4j.core.Logger;
030    import org.apache.logging.log4j.core.config.plugins.Plugin;
031    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
032    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
033    import 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)
056    public 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    }