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 */
017package org.apache.logging.log4j.core.filter;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Objects;
022
023import org.apache.logging.log4j.Level;
024import org.apache.logging.log4j.Marker;
025import org.apache.logging.log4j.ThreadContext;
026import org.apache.logging.log4j.core.Filter;
027import org.apache.logging.log4j.core.LogEvent;
028import org.apache.logging.log4j.core.Logger;
029import org.apache.logging.log4j.core.config.Node;
030import org.apache.logging.log4j.core.config.plugins.Plugin;
031import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginElement;
033import org.apache.logging.log4j.core.config.plugins.PluginFactory;
034import org.apache.logging.log4j.core.ContextDataInjector;
035import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
036import org.apache.logging.log4j.core.util.KeyValuePair;
037import org.apache.logging.log4j.message.Message;
038import org.apache.logging.log4j.util.PerformanceSensitive;
039import org.apache.logging.log4j.util.ReadOnlyStringMap;
040
041/**
042 * Compares against a log level that is associated with a context value. By default the context is the
043 * {@link ThreadContext}, but users may {@linkplain ContextDataInjectorFactory configure} a custom
044 * {@link ContextDataInjector} which obtains context data from some other source.
045 */
046@Plugin(name = "DynamicThresholdFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
047@PerformanceSensitive("allocation")
048public final class DynamicThresholdFilter extends AbstractFilter {
049
050    /**
051     * Creates a DynamicThresholdFilter.
052     * @param key The name of the key to compare.
053     * @param pairs An array of value and Level pairs.
054     * @param defaultThreshold The default Level.
055     * @param onMatch The action to perform if a match occurs.
056     * @param onMismatch The action to perform if no match occurs.
057     * @return The DynamicThresholdFilter.
058     */
059    // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
060    @PluginFactory
061    public static DynamicThresholdFilter createFilter(
062            @PluginAttribute("key") final String key,
063            @PluginElement("Pairs") final KeyValuePair[] pairs,
064            @PluginAttribute("defaultThreshold") final Level defaultThreshold,
065            @PluginAttribute("onMatch") final Result onMatch,
066            @PluginAttribute("onMismatch") final Result onMismatch) {
067        final Map<String, Level> map = new HashMap<>();
068        for (final KeyValuePair pair : pairs) {
069            map.put(pair.getKey(), Level.toLevel(pair.getValue()));
070        }
071        final Level level = defaultThreshold == null ? Level.ERROR : defaultThreshold;
072        return new DynamicThresholdFilter(key, map, level, onMatch, onMismatch);
073    }
074
075    private Level defaultThreshold = Level.ERROR;
076    private final String key;
077    private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector();
078    private Map<String, Level> levelMap = new HashMap<>();
079
080    private DynamicThresholdFilter(final String key, final Map<String, Level> pairs, final Level defaultLevel,
081                                   final Result onMatch, final Result onMismatch) {
082        super(onMatch, onMismatch);
083        Objects.requireNonNull(key, "key cannot be null");
084        this.key = key;
085        this.levelMap = pairs;
086        this.defaultThreshold = defaultLevel;
087    }
088
089    @Override
090    public boolean equals(final Object obj) {
091        if (this == obj) {
092            return true;
093        }
094        if (!super.equalsImpl(obj)) {
095            return false;
096        }
097        if (getClass() != obj.getClass()) {
098            return false;
099        }
100        final DynamicThresholdFilter other = (DynamicThresholdFilter) obj;
101        if (defaultThreshold == null) {
102            if (other.defaultThreshold != null) {
103                return false;
104            }
105        } else if (!defaultThreshold.equals(other.defaultThreshold)) {
106            return false;
107        }
108        if (key == null) {
109            if (other.key != null) {
110                return false;
111            }
112        } else if (!key.equals(other.key)) {
113            return false;
114        }
115        if (levelMap == null) {
116            if (other.levelMap != null) {
117                return false;
118            }
119        } else if (!levelMap.equals(other.levelMap)) {
120            return false;
121        }
122        return true;
123    }
124
125    private Result filter(final Level level, final ReadOnlyStringMap contextMap) {
126        final String value = contextMap.getValue(key);
127        if (value != null) {
128            Level ctxLevel = levelMap.get(value);
129            if (ctxLevel == null) {
130                ctxLevel = defaultThreshold;
131            }
132            return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch;
133        }
134        return Result.NEUTRAL;
135
136    }
137
138    @Override
139    public Result filter(final LogEvent event) {
140        return filter(event.getLevel(), event.getContextData());
141    }
142
143    @Override
144    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
145                         final Throwable t) {
146        return filter(level, currentContextData());
147    }
148
149    @Override
150    public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
151                         final Throwable t) {
152        return filter(level, currentContextData());
153    }
154
155    @Override
156    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
157                         final Object... params) {
158        return filter(level, currentContextData());
159    }
160
161    private ReadOnlyStringMap currentContextData() {
162        return injector.rawContextData();
163    }
164
165    @Override
166    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
167            final Object p0) {
168        return filter(level, currentContextData());
169    }
170
171    @Override
172    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
173            final Object p0, final Object p1) {
174        return filter(level, currentContextData());
175    }
176
177    @Override
178    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
179            final Object p0, final Object p1, final Object p2) {
180        return filter(level, currentContextData());
181    }
182
183    @Override
184    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
185            final Object p0, final Object p1, final Object p2, final Object p3) {
186        return filter(level, currentContextData());
187    }
188
189    @Override
190    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
191            final Object p0, final Object p1, final Object p2, final Object p3,
192            final Object p4) {
193        return filter(level, currentContextData());
194    }
195
196    @Override
197    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
198            final Object p0, final Object p1, final Object p2, final Object p3,
199            final Object p4, final Object p5) {
200        return filter(level, currentContextData());
201    }
202
203    @Override
204    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
205            final Object p0, final Object p1, final Object p2, final Object p3,
206            final Object p4, final Object p5, final Object p6) {
207        return filter(level, currentContextData());
208    }
209
210    @Override
211    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
212            final Object p0, final Object p1, final Object p2, final Object p3,
213            final Object p4, final Object p5, final Object p6,
214            final Object p7) {
215        return filter(level, currentContextData());
216    }
217
218    @Override
219    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
220            final Object p0, final Object p1, final Object p2, final Object p3,
221            final Object p4, final Object p5, final Object p6,
222            final Object p7, final Object p8) {
223        return filter(level, currentContextData());
224    }
225
226    @Override
227    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
228            final Object p0, final Object p1, final Object p2, final Object p3,
229            final Object p4, final Object p5, final Object p6,
230            final Object p7, final Object p8, final Object p9) {
231        return filter(level, currentContextData());
232    }
233
234    public String getKey() {
235        return this.key;
236    }
237
238    public Map<String, Level> getLevelMap() {
239        return levelMap;
240    }
241
242    @Override
243    public int hashCode() {
244        final int prime = 31;
245        int result = super.hashCodeImpl();
246        result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode());
247        result = prime * result + ((key == null) ? 0 : key.hashCode());
248        result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode());
249        return result;
250    }
251
252    @Override
253    public String toString() {
254        final StringBuilder sb = new StringBuilder();
255        sb.append("key=").append(key);
256        sb.append(", default=").append(defaultThreshold);
257        if (levelMap.size() > 0) {
258            sb.append('{');
259            boolean first = true;
260            for (final Map.Entry<String, Level> entry : levelMap.entrySet()) {
261                if (!first) {
262                    sb.append(", ");
263                    first = false;
264                }
265                sb.append(entry.getKey()).append('=').append(entry.getValue());
266            }
267            sb.append('}');
268        }
269        return sb.toString();
270    }
271}