[英]How to Change log level for particular users/threads at runtime
我正在使用slf4j與log4j 2.0或logback作為實現。 例如,我的servlet有一個級別為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<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
雖然現有的答案可能有效(沒有親自嘗試過),但經過深入搜索后,我發現了一個非常簡單明了的技巧來完成你的要求。
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
的值,該customLogLevel
中的所有日志調用都將具有與匹配的KeyValuePair
行對應的日志級別。 因此在上面的示例中,線程中的所有日志調用都將具有DEBUG
級別。
我遇到了同樣的問題,最后通過更改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,您可以將它與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使用配置文件中的預定義級別作為動態閾值,而此過濾器使用地圖中以編程方式定義的級別。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.