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 org.apache.logging.log4j.Level;
020    import org.apache.logging.log4j.Marker;
021    import org.apache.logging.log4j.core.LogEvent;
022    import org.apache.logging.log4j.core.Logger;
023    import org.apache.logging.log4j.core.config.plugins.Plugin;
024    import org.apache.logging.log4j.core.config.plugins.PluginAttr;
025    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
026    import org.apache.logging.log4j.message.Message;
027    
028    import java.util.Iterator;
029    import java.util.Locale;
030    import java.util.Queue;
031    import java.util.concurrent.ConcurrentLinkedQueue;
032    import java.util.concurrent.DelayQueue;
033    import java.util.concurrent.Delayed;
034    import java.util.concurrent.TimeUnit;
035    
036    /**
037     * The <code>BurstFilter</code> is a logging filter that regulates logging
038     * traffic. Use this filter when you want to control the maximum burst of log
039     * statements that can be sent to an appender. The filter is configured in the
040     * log4j configuration file. For example, the following configuration limits the
041     * number of INFO level (as well as DEBUG and TRACE) log statements that can be sent to the
042     * console to a burst of 100 with an average rate of 16 per second. WARN, ERROR and FATAL messages would continue to
043     * be delivered.<br>
044     * <br>
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", type = "Core", elementType = "filter", printObject = true)
057    public 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<LogDelay>();
078    
079        private final Queue<LogDelay> available = new ConcurrentLinkedQueue<LogDelay>();
080    
081        private BurstFilter(Level level, float rate, long maxBurst, Result onMatch, 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(Logger logger, Level level, Marker marker, String msg, Object[] params) {
092            return filter(level);
093        }
094    
095        @Override
096        public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
097            return filter(level);
098        }
099    
100        @Override
101        public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
102            return filter(level);
103        }
104    
105        @Override
106        public Result filter(LogEvent event) {
107            return filter(event.getLevel());
108        }
109    
110        /**
111         * Decide if we're going to log <code>event</code> based on whether the
112         * maximum burst of log statements has been exceeded.
113         *
114         * @param level The log level.
115         * @return The onMatch value if the filter passes, onMismatch otherwise.
116         */
117        private Result filter(Level level) {
118            if (this.level.isAtLeastAsSpecificAs(level)) {
119                LogDelay delay = history.poll();
120                while (delay != null) {
121                    available.add(delay);
122                    delay = history.poll();
123                }
124                delay = available.poll();
125                if (delay != null) {
126                    delay.setDelay(burstInterval);
127                    history.add(delay);
128                    return onMatch;
129                }
130                return onMismatch;
131            }
132            return onMatch;
133    
134        }
135    
136        /**
137         * Returns the number of available slots. Used for unit testing.
138         * @return The number of available slots.
139         */
140        public int getAvailable() {
141            return available.size();
142        }
143    
144        /**
145         * Clear the history. Used for unit testing.
146         */
147        public void clear() {
148            Iterator<LogDelay> iter = history.iterator();
149            while (iter.hasNext()) {
150                LogDelay delay = iter.next();
151                history.remove(delay);
152                available.add(delay);
153            }
154        }
155    
156        @Override
157        public String toString() {
158            return "level=" + level.toString() + ", interval=" + burstInterval + ", max=" + history.size();
159        }
160    
161        /**
162         * Delay object to represent each log event that has occurred within the timespan.
163         */
164        private class LogDelay implements Delayed {
165    
166            private long expireTime;
167    
168            public LogDelay() {
169            }
170    
171            public void setDelay(long delay) {
172                this.expireTime = delay + System.nanoTime();
173            }
174    
175            public long getDelay(TimeUnit timeUnit) {
176                return timeUnit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
177            }
178    
179            public int compareTo(Delayed delayed) {
180                if (this.expireTime < ((LogDelay) delayed).expireTime) {
181                    return -1;
182                } else if (this.expireTime > ((LogDelay) delayed).expireTime) {
183                    return 1;
184                }
185                return 0;
186            }
187    
188            @Override
189            public boolean equals(Object o) {
190                if (this == o) {
191                    return true;
192                }
193                if (o == null || getClass() != o.getClass()) {
194                    return false;
195                }
196    
197                LogDelay logDelay = (LogDelay) o;
198    
199                if (expireTime != logDelay.expireTime) {
200                    return false;
201                }
202    
203                return true;
204            }
205    
206            @Override
207            public int hashCode() {
208                return (int) (expireTime ^ (expireTime >>> HASH_SHIFT));
209            }
210        }
211    
212        /**
213         * @param levelName  The logging level.
214         * @param rate   The average number of events per second to allow.
215         * @param maxBurst  The maximum number of events that can occur before events are filtered for exceeding the
216         * average rate. The default is 10 times the rate.
217         * @param match  The Result to return when the filter matches. Defaults to Result.NEUTRAL.
218         * @param mismatch The Result to return when the filter does not match. The default is Result.DENY.
219         * @return A BurstFilter.
220         */
221        @PluginFactory
222        public static BurstFilter createFilter(@PluginAttr("level") String levelName,
223                                               @PluginAttr("rate") String rate,
224                                               @PluginAttr("maxBurst") String maxBurst,
225                                               @PluginAttr("onmatch") String match,
226                                               @PluginAttr("onmismatch") String mismatch) {
227            Result onMatch = Result.toResult(match, Result.NEUTRAL);
228            Result onMismatch = Result.toResult(mismatch, Result.DENY);
229            Level level = Level.toLevel(levelName, Level.WARN);
230            float eventRate = rate == null ? DEFAULT_RATE : Float.parseFloat(rate);
231            if (eventRate <= 0) {
232                eventRate = DEFAULT_RATE;
233            }
234            long max = maxBurst == null ? (long) (eventRate * DEFAULT_RATE_MULTIPLE) : Long.parseLong(maxBurst);
235            return new BurstFilter(level, eventRate, max, onMatch, onMismatch);
236        }
237    }