我有一个带有 log4j 的 Struts 应用程序来显示有关应用程序的信息。
格式化日志输出的模式如下:
log4j.appender.RALL.layout.ConversionPattern=[%p] %d{dd/MM/yyyy HH:mm:ss} [THREAD ID=%t] [CLASS=(%C{1}:%L)] %m%n
我需要在日志中显示线程 ID而不是线程名称。显示线程名称的转换字符是 %t。我在 log4j 文档中没有看到获取它的方法。
谁能帮我??
这是可能的,但并不像使用一些预配置的模式那么容易。
Log4j 1.X 和 Log4j 2.x 没有任何用于打印线程 ID 的预配置模式,但您始终可以使用一些“魔术”。
PatternLayout
正在使用PatternParser
标记为final
类并具有“模式”的静态映射作为键和Converters
类作为值的类。每次当 Parses 发现使用用于记录模式格式的模式时,%
它都会使用与 map 中的此模式键匹配的转换器。
您不能将自己的规则添加到该地图,但您仍然可以编写自己的 MyOwnPatternLayout:
public class MyOwnPatternLayout extends PatternLayout
这将在它的format
方法中做这样的伎俩:
public String format(LoggingEvent event) {
String log = super.format(event);
/*
Now you just have to replace with regex all occurences of %i or
any mark you would like to use as mark to represent Thread ID
with Thread ID value.
Only thing you have to be sure to not use any mark as your Thread ID
that already is defined by PatterParser class
*/
return log.replaceAll("%i", someThreadID);
}
唯一的问题是您必须以某种方式获取该线程 ID。有时您所要做的就是解析可以轻松收集的线程名称:
String threadName = event.getThreadName();
例如 Apache-Tomcat 将线程 ID 放在线程名称http-nio-/127.0.0.1-8084"-exec-41的末尾。
为了确保线程 ID 正确,您还可以创建自己的 LogginEvent 和 Logger 子类(MyLoggingEvent 和 MyLogger),并在 MyLogger 中创建 MyLoggingEvent,女巫也将线程 ID 作为参数,而不仅仅是线程名称。然后你可以很容易地在上面的代码中收集它。
一种方法是使用 log4j MDC 自己添加它。我们使用它来为 Web 请求添加用户名。我们在每个请求开始时的过滤器中执行此操作。例如。
import org.apache.log4j.MDC;
...
// Add username to MDC
String username = ...;
MDC.put("user", username);
然后添加[%X{user}]
到您的转换模式。
我为即将到来的 2.6 实现了线程 ID 和线程优先级。在此处跟踪:https ://issues.apache.org/jira/browse/LOG4J2-1299
您可以从 Apache 快照存储库中获取 2.6-SNAPSHOT 构建:https ://repository.apache.org/content/repositories/snapshots/
您可以使用 ThreadContext Map 向 log4j2 提供元数据。这是您可以通过正常格式添加的值的字符串映射。
String threadId = String.valueOf(Thread.currentThread().getId());
ThreadContext.put("TId", threadId);
还有一个更合理的模式:
<PatternLayout pattern="%d{yyyyMMdd}T%d{HHmmss.SSS} %-5level [%t] [%5X{TId}] %15c{1} - %msg%n"/>
我不知道它是什么时候引入的,但在 log4j2 我们有% tid
输出生成日志事件的线程 ID。
我认为不可能使用标准 log4j 格式显示线程 ID。我还通过PatterParser
类代码进行了调查,没有发现任何有用的东西。我找到了一些自定义解决方案,但仅适用于具有%i
选项的 IBM 服务器:
%i:插入线程 ID。与线程名称(由 %t 表示)不同,这是线程的数字 ID。请注意,此参数是特定于 Initiate 的,而此处列出的其他参数是 log4j 的标准参数。
看到这个链接
如下扩展PatternLayout
,然后在格式字符串中指定MyPatternLayout
with 。$X{threadId}
此实现用于ThreadLocal
最小化计算线程 ID 的性能影响:
MyPatternLayout extends PatternLayout {
private final ThreadLocal<String> threadId = new ThreadLocal<String>() {
@Override
protected String initialValue() {
String t = Long.toString(Thread.currentThread().getId());
MDC.put("threadId", t);
return t;
}
};
@Override
public String format(LoggingEvent event) {
this.threadId.get();
return super.format(event);
}
}
一种可能的解决方案是创建您自己的类,该类位于您的代码和 Log4J 之间,并将线程 ID 附加到每个日志消息:
public class ThreadLogger
{
// Constructor declared private to prevent instantiation. Use static methods instead.
private ThreadLogger() {}
private static enum LogLevel
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR
}
public static void trace(String message)
{
logMessage(message, LogLevel.ERROR);
}
public static void debug(String message)
{
logMessage(message, LogLevel.ERROR);
}
public static void info(String message)
{
logMessage(message, LogLevel.ERROR);
}
public static void warn(String message)
{
logMessage(message, LogLevel.WARN);
}
public static void error(String message)
{
logMessage(message, LogLevel.ERROR);
}
private static void logMessage(String message, LogLevel logLevel)
{
// Get the Log4J logger for the class that originally wanted to log the message
String callingClassName = Thread.currentThread().getStackTrace()[3].getClassName();
Class callingClass;
try
{
callingClass = Class.forName(callingClassName);
}
catch(ClassNotFoundException e)
{
String errorMessage = String.format("Could not reference class [%s]. Unable to log call!", callingClassName);
throw new RuntimeException(errorMessage);
}
Logger logger = Logger.getLogger(callingClass);
// Get the thread ID and place it in front of the logged message
long threadId = Thread.currentThread().getId();
String formattedMessage = String.format("[%s] %s", threadId, message);
// Log the message
switch(logLevel)
{
case TRACE:
logger.trace(formattedMessage);
break;
case DEBUG:
logger.debug(formattedMessage);
break;
case INFO:
logger.info(formattedMessage);
break;
case WARN:
logger.warn(formattedMessage);
break;
case ERROR:
logger.error(formattedMessage);
break;
}
}
}
缺点:
1234 [main] INFO com.foo.bar.Baz - [1] Hello world on thread #1!
1234 [main] INFO com.foo.bar.Baz - [2] Hello world on thread #2!
我创建自己的附加程序并将 Thread.currentThread().getId() 设置为 MDC 属性。%X{threadId} 应该给我线程 ID。此解决方案从 1.2.15 开始运行。然后,您可以将 AsyncAppender 附加到此。
public class CurrentThreadIdAppender extends AppenderSkeleton implements AppenderAttachable {
private final AppenderAttachableImpl appenders = new AppenderAttachableImpl();
...
@Override
protected void append(LoggingEvent event) {
synchronized (appenders) {
event.setProperty("threadId", String.valueOf(Thread.currentThread().getId()));
appenders.appendLoopOnAppenders(event);
}
}
...
}
log4j2 的另一个优雅解决方案是使用org.apache.logging.log4j.core.pattern.LogEventPatternConverter
.
你可以写一个这样的类
@Plugin(name = "ThreadIdConverter", category = "Converter")
@ConverterKeys({ "tid" })
public class ThreadIdConverter extends LogEventPatternConverter {
protected ThreadIdConverter(String name, String style) {
super(name, style);
}
@Override
public void format(LogEvent event, StringBuilder toAppendTo) {
toAppendTo.append(getThreadId());
}
protected String getThreadId() {
long id = Thread.currentThread().getId();
return Long.toHexString(id);
}
public static ThreadIdConverter newInstance(String[] options) {
return new ThreadIdConverter("tid", "tid");
}
}
通过这种方式,您正在创建一个新模式tid
,并且可以在定义 appender 的布局时使用它
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>%d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n</Pattern>
</PatternLayout>
</Console>
</Appenders>
最后要记住的重要事情是如何激活您的 log4j2 插件。为此,您必须使用节点上的package
属性在log4j2 配置文件中添加包含插件的包Configuration
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE Configuration>
<Configuration status="warn"
packages="my.package.logging.plugins">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>%d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n</Pattern>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="console" />
</Root>
<Logger name="my.package" level="trace" />
</Loggers>
</Configuration>