2013-01-16 136 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); 
     } 
    }