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 (!Objects.equals(defaultThreshold, other.defaultThreshold)) { 102 return false; 103 } 104 if (!Objects.equals(key, other.key)) { 105 return false; 106 } 107 if (!Objects.equals(levelMap, other.levelMap)) { 108 return false; 109 } 110 return true; 111 } 112 113 private Result filter(final Level level, final ReadOnlyStringMap contextMap) { 114 final String value = contextMap.getValue(key); 115 if (value != null) { 116 Level ctxLevel = levelMap.get(value); 117 if (ctxLevel == null) { 118 ctxLevel = defaultThreshold; 119 } 120 return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch; 121 } 122 return Result.NEUTRAL; 123 124 } 125 126 @Override 127 public Result filter(final LogEvent event) { 128 return filter(event.getLevel(), event.getContextData()); 129 } 130 131 @Override 132 public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, 133 final Throwable t) { 134 return filter(level, currentContextData()); 135 } 136 137 @Override 138 public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, 139 final Throwable t) { 140 return filter(level, currentContextData()); 141 } 142 143 @Override 144 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 145 final Object... params) { 146 return filter(level, currentContextData()); 147 } 148 149 private ReadOnlyStringMap currentContextData() { 150 return injector.rawContextData(); 151 } 152 153 @Override 154 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 155 final Object p0) { 156 return filter(level, currentContextData()); 157 } 158 159 @Override 160 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 161 final Object p0, final Object p1) { 162 return filter(level, currentContextData()); 163 } 164 165 @Override 166 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 167 final Object p0, final Object p1, final Object p2) { 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, final Object p2, final Object p3) { 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, final Object p3, 180 final Object p4) { 181 return filter(level, currentContextData()); 182 } 183 184 @Override 185 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 186 final Object p0, final Object p1, final Object p2, final Object p3, 187 final Object p4, final Object p5) { 188 return filter(level, currentContextData()); 189 } 190 191 @Override 192 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 193 final Object p0, final Object p1, final Object p2, final Object p3, 194 final Object p4, final Object p5, final Object p6) { 195 return filter(level, currentContextData()); 196 } 197 198 @Override 199 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 200 final Object p0, final Object p1, final Object p2, final Object p3, 201 final Object p4, final Object p5, final Object p6, 202 final Object p7) { 203 return filter(level, currentContextData()); 204 } 205 206 @Override 207 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 208 final Object p0, final Object p1, final Object p2, final Object p3, 209 final Object p4, final Object p5, final Object p6, 210 final Object p7, final Object p8) { 211 return filter(level, currentContextData()); 212 } 213 214 @Override 215 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 216 final Object p0, final Object p1, final Object p2, final Object p3, 217 final Object p4, final Object p5, final Object p6, 218 final Object p7, final Object p8, final Object p9) { 219 return filter(level, currentContextData()); 220 } 221 222 public String getKey() { 223 return this.key; 224 } 225 226 public Map<String, Level> getLevelMap() { 227 return levelMap; 228 } 229 230 @Override 231 public int hashCode() { 232 final int prime = 31; 233 int result = super.hashCodeImpl(); 234 result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode()); 235 result = prime * result + ((key == null) ? 0 : key.hashCode()); 236 result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode()); 237 return result; 238 } 239 240 @Override 241 public String toString() { 242 final StringBuilder sb = new StringBuilder(); 243 sb.append("key=").append(key); 244 sb.append(", default=").append(defaultThreshold); 245 if (levelMap.size() > 0) { 246 sb.append('{'); 247 boolean first = true; 248 for (final Map.Entry<String, Level> entry : levelMap.entrySet()) { 249 if (!first) { 250 sb.append(", "); 251 first = false; 252 } 253 sb.append(entry.getKey()).append('=').append(entry.getValue()); 254 } 255 sb.append('}'); 256 } 257 return sb.toString(); 258 } 259}