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    @PluginFactory
060    public static DynamicThresholdFilter createFilter(
061            @PluginAttribute("key") final String key,
062            @PluginElement("Pairs") final KeyValuePair[] pairs,
063            @PluginAttribute("defaultThreshold") final Level defaultThreshold,
064            @PluginAttribute("onMatch") final Result onMatch,
065            @PluginAttribute("onMismatch") final Result onMismatch) {
066        final Map<String, Level> map = new HashMap<>();
067        for (final KeyValuePair pair : pairs) {
068            map.put(pair.getKey(), Level.toLevel(pair.getValue()));
069        }
070        final Level level = defaultThreshold == null ? Level.ERROR : defaultThreshold;
071        return new DynamicThresholdFilter(key, map, level, onMatch, onMismatch);
072    }
073
074    private Level defaultThreshold = Level.ERROR;
075    private final String key;
076    private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector();
077    private Map<String, Level> levelMap = new HashMap<>();
078
079    private DynamicThresholdFilter(final String key, final Map<String, Level> pairs, final Level defaultLevel,
080                                   final Result onMatch, final Result onMismatch) {
081        super(onMatch, onMismatch);
082        Objects.requireNonNull(key, "key cannot be null");
083        this.key = key;
084        this.levelMap = pairs;
085        this.defaultThreshold = defaultLevel;
086    }
087
088    @Override
089    public boolean equals(final Object obj) {
090        if (this == obj) {
091            return true;
092        }
093        if (!super.equalsImpl(obj)) {
094            return false;
095        }
096        if (getClass() != obj.getClass()) {
097            return false;
098        }
099        final DynamicThresholdFilter other = (DynamicThresholdFilter) obj;
100        if (defaultThreshold == null) {
101            if (other.defaultThreshold != null) {
102                return false;
103            }
104        } else if (!defaultThreshold.equals(other.defaultThreshold)) {
105            return false;
106        }
107        if (key == null) {
108            if (other.key != null) {
109                return false;
110            }
111        } else if (!key.equals(other.key)) {
112            return false;
113        }
114        if (levelMap == null) {
115            if (other.levelMap != null) {
116                return false;
117            }
118        } else if (!levelMap.equals(other.levelMap)) {
119            return false;
120        }
121        return true;
122    }
123
124    private Result filter(final Level level, final ReadOnlyStringMap contextMap) {
125        final String value = contextMap.getValue(key);
126        if (value != null) {
127            Level ctxLevel = levelMap.get(value);
128            if (ctxLevel == null) {
129                ctxLevel = defaultThreshold;
130            }
131            return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch;
132        }
133        return Result.NEUTRAL;
134
135    }
136
137    @Override
138    public Result filter(final LogEvent event) {
139        return filter(event.getLevel(), event.getContextData());
140    }
141
142    @Override
143    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
144                         final Throwable t) {
145        return filter(level, currentContextData());
146    }
147
148    @Override
149    public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
150                         final Throwable t) {
151        return filter(level, currentContextData());
152    }
153
154    @Override
155    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
156                         final Object... params) {
157        return filter(level, currentContextData());
158    }
159
160    private ReadOnlyStringMap currentContextData() {
161        return injector.rawContextData();
162    }
163
164    @Override
165    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
166            final Object p0) {
167        return filter(level, currentContextData());
168    }
169
170    @Override
171    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
172            final Object p0, final Object p1) {
173        return filter(level, currentContextData());
174    }
175
176    @Override
177    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
178            final Object p0, final Object p1, final Object p2) {
179        return filter(level, currentContextData());
180    }
181
182    @Override
183    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
184            final Object p0, final Object p1, final Object p2, final Object p3) {
185        return filter(level, currentContextData());
186    }
187
188    @Override
189    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
190            final Object p0, final Object p1, final Object p2, final Object p3,
191            final Object p4) {
192        return filter(level, currentContextData());
193    }
194
195    @Override
196    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
197            final Object p0, final Object p1, final Object p2, final Object p3,
198            final Object p4, final Object p5) {
199        return filter(level, currentContextData());
200    }
201
202    @Override
203    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
204            final Object p0, final Object p1, final Object p2, final Object p3,
205            final Object p4, final Object p5, final Object p6) {
206        return filter(level, currentContextData());
207    }
208
209    @Override
210    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
211            final Object p0, final Object p1, final Object p2, final Object p3,
212            final Object p4, final Object p5, final Object p6,
213            final Object p7) {
214        return filter(level, currentContextData());
215    }
216
217    @Override
218    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
219            final Object p0, final Object p1, final Object p2, final Object p3,
220            final Object p4, final Object p5, final Object p6,
221            final Object p7, final Object p8) {
222        return filter(level, currentContextData());
223    }
224
225    @Override
226    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
227            final Object p0, final Object p1, final Object p2, final Object p3,
228            final Object p4, final Object p5, final Object p6,
229            final Object p7, final Object p8, final Object p9) {
230        return filter(level, currentContextData());
231    }
232
233    public String getKey() {
234        return this.key;
235    }
236
237    public Map<String, Level> getLevelMap() {
238        return levelMap;
239    }
240
241    @Override
242    public int hashCode() {
243        final int prime = 31;
244        int result = super.hashCodeImpl();
245        result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode());
246        result = prime * result + ((key == null) ? 0 : key.hashCode());
247        result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode());
248        return result;
249    }
250
251    @Override
252    public String toString() {
253        final StringBuilder sb = new StringBuilder();
254        sb.append("key=").append(key);
255        sb.append(", default=").append(defaultThreshold);
256        if (levelMap.size() > 0) {
257            sb.append('{');
258            boolean first = true;
259            for (final Map.Entry<String, Level> entry : levelMap.entrySet()) {
260                if (!first) {
261                    sb.append(", ");
262                    first = false;
263                }
264                sb.append(entry.getKey()).append('=').append(entry.getValue());
265            }
266            sb.append('}');
267        }
268        return sb.toString();
269    }
270}