如何在运行时更改特定用户/线程的日志级别

我正在使用slf4j与log4j 2.0或logback作为实现。 例如,我的s​​ervlet有一个级别为ERROR的记录器,我的服务器产生了servlet的100个线程。 我将在运行时获得一个特殊用户列表。 当我检测到一些连接的特殊用户时。我想将这些特殊用户/线程的日志级别更改为DEBUG,并使其他线程的日志级别不受影响(仍然是ERROR)。

我知道logback中的TurboFilter和log4j 2.0中的DynamicThresholdFilter,但由于我只会在运行时获取特殊用户列表,所以我无法使用它们。

这是我的申请:

package com.example.logging; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServlet; import org.slf4j.*; public class App extends HttpServlet { private final Logger Logger = LoggerFactory.getLogger(App.class); Map map = new HashMap(); public App() { map.put("user1", "DEBUG"); map.put("user2", "DEBUG"); map.put("user3", "ERROR"); } public void writeToLogFile(String userName) { if (map.containsKey(userName)) { // do something so that I can change the logger to the corresponding log level } Logger.error(userName + " error message"); // the logger is of level ERROR, so by default, this log event will not happen // but I want it to happen for special users if (Logger.isDebugEnabled()) { Logger.debug(userName + " debug message"); } } } 

这是我在log4j2.xml中的日志配置

                 

如果我调用以下方法:

 App myApp = new App(); // assume the below 4 methods are called concurrently myApp.writeToLogFile("user1"); myApp.writeToLogFile("user2"); myApp.writeToLogFile("user3"); myApp.writeToLogFile("user4"); 

预期的输出应该是:

 ERROR com.example.logging.App writeToLogFile - user1 error message DEBUG com.example.logging.App writeToLogFile - user1 debug message ERROR com.example.logging.App writeToLogFile - user2 error message DEBUG com.example.logging.App writeToLogFile - user2 debug message ERROR com.example.logging.App writeToLogFile - user3 error message ERROR com.example.logging.App writeToLogFile - user4 error message 

我遇到了同样的问题,最后通过更改DynamicThresholdFilter来使用我自己的filter

对应用程序的更改:

 package com.example.logging; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServlet; import org.slf4j.*; public class App extends HttpServlet { private final Logger Logger = LoggerFactory.getLogger(App.class); Map map = new HashMap(); public App() { map.put("user1", "Debug"); map.put("user2", "Debug"); map.put("user3", "Error"); } public void writeToLogFile(String userName) { // if the user is in the map, we put it into ThreadConext for filtering if (map.containsKey(userName)) { MDC.put("level", map.get(userName)); } Logger.error(userName + " error message"); if (Logger.isDebugEnabled()) { Logger.debug(userName + " debug message"); } // remember to remove it MDC.remove("level"); } } 

这是基于DynamicThresholdFilter的新定义的filter,我们称之为DynamicThresholdUserFilter,您可以将它与DynamicThresholdFilter的源代码进行比较

 package com.example.logging.log4j2.plugin; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.filter.AbstractFilter; import org.apache.logging.log4j.message.Message; /** * Compare against a log level that is associated with an MDC value. */ @Plugin(name = "DynamicThresholdUserFilter", category = "Core", elementType = "filter", printObject = true) public final class DynamicThresholdUserFilter extends AbstractFilter { private Level defaultThreshold = Level.ERROR; private final String key; private DynamicThresholdUserFilter(final String key, final Level defaultLevel, final Result onMatch, final Result onMismatch) { super(onMatch, onMismatch); if (key == null) { throw new NullPointerException("key cannot be null"); } this.key = key; this.defaultThreshold = defaultLevel; } public String getKey() { return this.key; } @Override public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { return filter(level); } @Override public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) { return filter(level); } @Override public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) { return filter(level); } @Override public Result filter(final LogEvent event) { return filter(event.getLevel()); } /* biggest change here */ private Result filter(final Level level) { final String value = ThreadContext.get(key); if (value != null) { Level ctxLevel = Level.toLevel(value); if (ctxLevel == null) { // in case the level is invalid ctxLevel = defaultThreshold; } return level.isAtLeastAsSpecificAs(ctxLevel) ? onMatch : onMismatch; } return Result.NEUTRAL; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("key=").append(key); sb.append(", default=").append(defaultThreshold); return sb.toString(); } /** * Create the DynamicThresholdFilter. * @param key The name of the key to compare. * @param pairs An array of value and Level pairs. * @param levelName The default Level. * @param match The action to perform if a match occurs. * @param mismatch The action to perform if no match occurs. * @return The DynamicThresholdFilter. */ @PluginFactory public static DynamicThresholdUserFilter createFilter( @PluginAttribute("key") final String key, @PluginAttribute("defaultThreshold") final String levelName, @PluginAttribute("onMatch") final String match, @PluginAttribute("onMismatch") final String mismatch) { final Result onMatch = Result.toResult(match); final Result onMismatch = Result.toResult(mismatch); final Level level = Level.toLevel(levelName, Level.ERROR); return new DynamicThresholdUserFilter(key, level, onMatch, onMismatch); } } 

将DynamicThresholdUserFilter和包名称添加到配置文件中

                    

新定义的filter与DynamicThresholdFilter非常相似。 不同之处在于DynamicThresholdFilter使用配置文件中的预定义级别作为动态阈值,而此filter使用地图中以编程方式定义的级别。

虽然现有的答案可能有效(没有亲自试过),经过深入搜索后,我发现了一个非常简单而巧妙的技巧来完成你的要求。

DynamicThresholdFilter可以与条件一起使用,以在运行时切换日志级别。 这与log4j2的ThreadContext相结合,你可以做很多漂亮的事情。

您必须根据用户名的自定义逻辑,在服务器调用处理开始时(在HttpServlet类的doFilter方法中的某个位置)填充ThreadContext中的特定键。 这看起来像是这样的:

 ThreadContext.put("customLogLevel", "debug"); 

然后在log4j2.xml文件中,将其作为全局filter放在根Configuration标记的正下方:

      

现在,根据您在调用开始时设置的ThreadContext中的键customLogLevel的值,该customLogLevel中的所有日志调用都将具有与匹配的KeyValuePair行对应的日志级别。 因此,在上面的示例中,线程中的所有日志调用都将具有DEBUG级别。