在日志中显示线程ID而不是线程名称

我有一个带有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发现使用以%开头的记录模式格式的模式时,它使用与地图中的此模式键匹配的转换器。

您无法将自己的规则添加到该地图,但您仍然可以编写自己的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请求的用户名。 我们在每个请求开始时在filter中执行此操作。 例如。

 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); 

而且更合理的模式:

   

关于“Fish Tagging”的完整Log4j2文档

我认为不可能用标准的log4j格式显示线程ID。 我还通过PatterParser类的代码调查了一个没有发现任何有用的东西。 我找到了一些自定义解决方案,但仅适用于具有%i选项的IBM服务器:

%i:插入线程ID。 与线程名称(由%t表示)不同,这是线程的数字ID。 请注意,此参数特定于Initiate,而此处列出的其他参数是log4j的标准参数。

看到这个链接

如下所示扩展PatternLayout ,然后在格式字符串中使用$X{threadId}指定MyPatternLayout

此实现使用ThreadLocal来最小化计算线程ID对性能的影响:

  MyPatternLayout extends PatternLayout { private final ThreadLocal threadId = new ThreadLocal() { @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; } } } 

缺点:

  • 性能? 这为每个日志语句添加了一些额外的步骤。
  • 稳定性? 这增加了潜在的失败点(Class.forName调用)。
  • 您必须通过调用新类替换所有现有的日志语句。
  • 在常规Log4J格式化之后,线程ID才会出现。 IE:
 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! 

我创建自己的appender并将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的布局时使用它

    %d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n    

要记住的最后一件重要事情是如何激活你的log4j2插件。 要做到这一点,您必须使用Configuration节点上的package属性在log4j2配置文件中添加包含插件的package

       %d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n