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.util.KeyValuePair;
035import org.apache.logging.log4j.message.Message;
036
037/**
038 * Compare against a log level that is associated with an MDC value.
039 */
040@Plugin(name = "DynamicThresholdFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
041public final class DynamicThresholdFilter extends AbstractFilter {
042
043    private static final long serialVersionUID = 1L;
044
045    /**
046     * Create the DynamicThresholdFilter.
047     * @param key The name of the key to compare.
048     * @param pairs An array of value and Level pairs.
049     * @param defaultThreshold The default Level.
050     * @param onMatch The action to perform if a match occurs.
051     * @param onMismatch The action to perform if no match occurs.
052     * @return The DynamicThresholdFilter.
053     */
054    @PluginFactory
055    public static DynamicThresholdFilter createFilter(
056            @PluginAttribute("key") final String key,
057            @PluginElement("Pairs") final KeyValuePair[] pairs,
058            @PluginAttribute("defaultThreshold") final Level defaultThreshold,
059            @PluginAttribute("onMatch") final Result onMatch,
060            @PluginAttribute("onMismatch") final Result onMismatch) {
061        final Map<String, Level> map = new HashMap<>();
062        for (final KeyValuePair pair : pairs) {
063            map.put(pair.getKey(), Level.toLevel(pair.getValue()));
064        }
065        final Level level = defaultThreshold == null ? Level.ERROR : defaultThreshold;
066        return new DynamicThresholdFilter(key, map, level, onMatch, onMismatch);
067    }
068    private Level defaultThreshold = Level.ERROR;
069    private final String key;
070
071    private Map<String, Level> levelMap = new HashMap<>();
072
073    private DynamicThresholdFilter(final String key, final Map<String, Level> pairs, final Level defaultLevel,
074                                   final Result onMatch, final Result onMismatch) {
075        super(onMatch, onMismatch);
076        Objects.requireNonNull(key, "key cannot be null");
077        this.key = key;
078        this.levelMap = pairs;
079        this.defaultThreshold = defaultLevel;
080    }
081
082    @Override
083    public boolean equals(final Object obj) {
084        if (this == obj) {
085            return true;
086        }
087        if (!super.equalsImpl(obj)) {
088            return false;
089        }
090        if (getClass() != obj.getClass()) {
091            return false;
092        }
093        final DynamicThresholdFilter other = (DynamicThresholdFilter) obj;
094        if (defaultThreshold == null) {
095            if (other.defaultThreshold != null) {
096                return false;
097            }
098        } else if (!defaultThreshold.equals(other.defaultThreshold)) {
099            return false;
100        }
101        if (key == null) {
102            if (other.key != null) {
103                return false;
104            }
105        } else if (!key.equals(other.key)) {
106            return false;
107        }
108        if (levelMap == null) {
109            if (other.levelMap != null) {
110                return false;
111            }
112        } else if (!levelMap.equals(other.levelMap)) {
113            return false;
114        }
115        return true;
116    }
117
118    private Result filter(final Level level) {
119        final Object value = ThreadContext.get(key);
120        if (value != null) {
121            Level ctxLevel = levelMap.get(value);
122            if (ctxLevel == null) {
123                ctxLevel = defaultThreshold;
124            }
125            return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch;
126        }
127        return Result.NEUTRAL;
128
129    }
130
131    @Override
132    public Result filter(final LogEvent event) {
133        return filter(event.getLevel());
134    }
135
136    @Override
137    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
138                         final Throwable t) {
139        return filter(level);
140    }
141
142    @Override
143    public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
144                         final Throwable t) {
145        return filter(level);
146    }
147
148    @Override
149    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
150                         final Object... params) {
151        return filter(level);
152    }
153
154    public String getKey() {
155        return this.key;
156    }
157
158    public Map<String, Level> getLevelMap() {
159        return levelMap;
160    }
161
162    @Override
163    public int hashCode() {
164        final int prime = 31;
165        int result = super.hashCodeImpl();
166        result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode());
167        result = prime * result + ((key == null) ? 0 : key.hashCode());
168        result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode());
169        return result;
170    }
171
172    @Override
173    public String toString() {
174        final StringBuilder sb = new StringBuilder();
175        sb.append("key=").append(key);
176        sb.append(", default=").append(defaultThreshold);
177        if (levelMap.size() > 0) {
178            sb.append('{');
179            boolean first = true;
180            for (final Map.Entry<String, Level> entry : levelMap.entrySet()) {
181                if (!first) {
182                    sb.append(", ");
183                    first = false;
184                }
185                sb.append(entry.getKey()).append('=').append(entry.getValue());
186            }
187            sb.append('}');
188        }
189        return sb.toString();
190    }
191}