2013-01-16 92 views
16

我有一个带有log4j的Struts应用程序来显示有关应用程序的信息。在日志中显示线程ID而不是线程名

的模式格式的日志输出如下:

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文档中看到获取它的方式。

任何人都可以帮助我?

+2

你尝试用'%i'? – partlov

+0

是的,我试过了,但正如你在下面说的,它只适用于IBM服务器 –

+0

@partlov这对我不起作用,'%i'无法识别,我正在使用log4j'1.2.16'。 –

回答

7

这可能但并不简单,只是使用一些预配置的模式。

Log4j 1.X和Log4j 2.x没有用于打印线程ID的任何预配置模式,但您始终可以使用一些“魔术技巧”。

PatternLayout使用PatternParser类,这是标记作为final类和具有“模式”作为键和Converters类作为值的静态映射。 Parse每次发现使用从%开始的记录模式格式的模式时,它将使用与该模式键匹配的转换器映射。

你不能自己的规则添加到地图,但你仍然可以编写自己的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和记录器(MyLoggingEvent和MyLogger)的子类,里面MyLogger创建MyLoggingEvent魔女也将作为参数线程ID不仅线程名。然后,您可以轻松地在上面的代码中收集它。

对不起,我希望这至少会给你一些帮助。

+0

在MyOwnPatternLayout中使用Thread.currentThread()。getId()? –

+0

Nop,因为当你使用'logger.info()'或'org.apache.log4j.Logger'中的其他方法时,就会创建新的LoggingEvent实例,它知道Thread创建它,但是当log被追加到文件或控制台或其他任何由你的'MyPatternLayout'发生的事情发生在其他线程中,并且有关使用'logger.info()的Thread的信息只存在于LoggingEvent实例中,换句话说它不能用于Thread.currentThread()。getId )'。正如我写的,你必须继承一个小的'Logger'类和'LoggingEvent'类。 – emka86

+0

谢谢,我认为解决方案是创建一个LoggingEvent子类并保留Logger默认类,因为我无法更改应用程序中的Logger类(超过500个使用它的类)。我如何将MyLoggingEvent类分配给Logger默认类? –

2

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

%i:插入线程ID。与线程名称(由%t表示)不同,这是线程的数字ID。 请注意,此参数特别针对启动,而此处列出的其他参数与log4j标准配合使用。

见你可以做this link

+4

它是否会(令人痛苦)触动别人,但我认为,尽管多次修改版本2和log4j版本2如此轻易/无用地将配置文件格式从属性更改为xml,但没有简单的方法添加线程ID,实际上对产品的使用有用吗?是否有可能我们都失去了一些东西? – stu

6

一种方法是把它自己使用log4j的MDC增加。我们使用它来为Web请求添加用户名。我们在每次请求开始时都会在过滤器中执行此操作。例如。

import org.apache.log4j.MDC; 

... 

    // Add username to MDC 
    String username = ...; 
    MDC.put("user", username); 

然后将[%X{user}]添加到您的转换模式。

3

您可以使用ThreadContext Map将元数据提供给log4j2。这是您通过正常格式添加的CAN值的字符串映射。

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"/> 

Full Log4j2 documentation on "Fish Tagging"

0

一个可能的解决方案是创建你自己的类,它位于你的代码和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!

0

创建我自己的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); 
     } 
    } 

... 

} 
0

与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> 
+1

如果你没有异步记录(例如使用AsyncLogger),这将会起作用! – Ph3n1x

1

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

此实现使用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); 
     } 
    }