【问题标题】:How to Change log level for particular users/threads at runtime如何在运行时更改特定用户/线程的日志级别
【发布时间】:2014-07-07 22:52:17
【问题描述】:

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

我知道 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<String, String> map = new HashMap<String, String>();

    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 中的日志配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="ERROR">
<Appenders>
    <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="%-5level %class{36} %M %msg%xEx%n" />
    </Console>
</Appenders>
<Loggers>
    <Logger name="com.example.logging.App" level="ERROR" additivity="false">
            <AppenderRef ref="Console" />
    </Logger>
    <Root level="DEBUG">
        <AppenderRef ref="Console" />
    </Root>
</Loggers> 
</Configuration>

如果我调用以下方法:

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

【问题讨论】:

  • 我不确定,我知道 ThreadContext 可以用于区分日志消息和线程,但我不知道它是否可以用于更改单个线程的日志级别。
  • 你是如何创建你的记录器的?如果您想为每个线程配置设置,那么大概您在运行前不知道该线程是什么(即您没有在某些属性文件或其他东西中进行配置)。你不能只为你正在使用的对象的每个实例配置它吗?或者也许让它成为线程本地的,这样对它的更改只影响当前线程?
  • 其实这个类是用来服务客户端的,所以我可以从当前线程中知道客户端信息,所以对于一两个特定的客户端,我希望能够为他们动态更改日志级别,如果它们连接到我的 servlet,不会影响其他人。
  • 我之前的评论应该有效。

标签: java multithreading logging logback log4j2


【解决方案1】:

我遇到了同样的问题,我最终通过更改 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<String, String> map = new HashMap<String, String>();

    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新定义的过滤器,我们称之为DynamicThresholdUserFilter,可以和the source code of 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 和包名称添加到您的配置文件中

<?xml version="1.0" encoding="UTF-8"?>
<!-- add the package name of the filter-->
<Configuration status="ERROR" packages="com.example.logging.plugin">
    <!-- configuration of the new defined filter -->
    <DynamicThresholdUserFilter key="level" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL" />
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%-5level %class{36} %M %msg%xEx%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="com.example.logging.App" level="ERROR" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
        <Root level="debug">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

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

【讨论】:

  • 非常感谢您的回答。知道如何使用 Logback 达到同样的效果吗?
【解决方案2】:

虽然已经存在的答案可能有效(没有亲自尝试过),但经过深入搜索后,我发现了一个非常简单而巧妙的技巧来满足您的要求。

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

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

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

然后在你的 log4j2.xml 文件中,你把它作为一个全局过滤器,就在根 Configuration 标签的正下方:

<DynamicThresholdFilter key="customLogLevel" onMatch="ACCEPT" onMismatch="NEUTRAL">
    <KeyValuePair key="debug" value="DEBUG"/>
    <KeyValuePair key="error" value="ERROR"/>
    <KeyValuePair key="info" value="INFO"/>
</DynamicThresholdFilter>

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

【讨论】:

  • 这种方法在使用这种方式时似乎会覆盖任何包的日志级别。我认为这是因为它不会改变根记录器的日志级别。有没有办法做到这一点,就像你改变了默认的根日志级别一样?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-11
  • 2021-12-21
  • 2015-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多