1. 程式人生 > >多執行緒環境下使用log4j輸出各執行緒的標識,區分各執行緒輸出的內容

多執行緒環境下使用log4j輸出各執行緒的標識,區分各執行緒輸出的內容

在多執行緒環境下,我們可能需要輸出很多資訊,每個執行緒產生的日誌資訊可能都是類似的,我們如何區分出哪些資訊是同一個執行緒輸出的呢?其實log4j已經提供了多種實現方式:

1.使用PatternLayout,在設定輸出格式的時候增加%t引數,這樣會輸出各個執行緒的執行緒名稱,這樣我們就可以根據執行緒名稱區分哪些內容是同一個執行緒輸出出來的。

2.使用NDC,也是基於PatternLayout,在設定輸出格式的時候增加%x引數,不過需要再程式碼中增加相關內容,再執行緒開始的時候,呼叫 NDC.push("標識資訊");在這之後,所有的輸出的日誌內容%x的位置都會輸出指定的"標識資訊",這樣也能區分開不同執行緒輸出的內容,NDC內 部是各堆疊實現的,所以可以多次呼叫push方法加入"標識資訊",然後呼叫pop方法"彈出"當前表示資訊,使上一個標識資訊可用,最後,載執行緒處理結 束先,要再次呼叫NDC.remove();方法,清空堆疊資訊。

3.使用MDC,和NDC類似,都是基於PatternLayout,的,需要使用%X{paramName}引數(注意MDC使用大寫 'X',NDC使用小寫'x'),不過MDC可以同時指定多個%X,例如:%X{param1} %X{param2},在程式中處理的時候也需要程式碼配合,開始的時候需要呼叫MDC.put(String key,Object o);方法一次或多次,將輸出格式中的%X佔位符指定實際的標識資訊,然後再正常輸出日誌資訊,因為MDC內部是用MAP實現的,而不是堆疊,所以最後不 用清理MAP內容。

以上三種方式是log4j已經提供的方式,但是都有其缺點:

1.輸出的是執行緒名稱,當tomcat環境下使用的時候,個別執行緒名稱會比較長,影響日誌資訊的可讀性,而且我們只是為了區別各執行緒輸出的資訊,對執行緒名稱具體是什麼內容不關心。

2和3的實現方式類似,不過需要程式碼的支援,每次使用時候,需要設定引數的內容,然後才能使用,如果忘記,可能會帶來一些影響。

另外還有一個方法就是擴充套件log4j的某些類,在log4j的日誌中輸出執行緒的ID(從jdk5開始,Thread類增加了getId()方法), 執行緒ID是個long型數字,大多數情況下可能只有幾十到幾百之間的數字,比較短,足夠作為區分執行緒的標識用了,擴充套件方式如下:

首先擴充套件PatternLayout類,

package ex.log4j;

import org.apache.log4j.PatternLayout;
import org.apache.log4j.helpers.PatternParser;

import ex.log4j.helper.ExPatternParser;

public class ExPatternLayout extends PatternLayout {

 public ExPatternLayout(String pattern) {
  super(pattern);
 }

 public ExPatternLayout() {
  super();
 }
 
 /**
  * 重寫createPatternParser方法,返回PatternParser的子類
  */
 @Override
 protected PatternParser createPatternParser(String pattern) {
  return new ExPatternParser(pattern);
 }
}

擴充套件PatternParser類,

package ex.log4j.helper;

import org.apache.log4j.helpers.FormattingInfo;
import org.apache.log4j.helpers.PatternConverter;
import org.apache.log4j.helpers.PatternParser;
import org.apache.log4j.spi.LoggingEvent;

public class ExPatternParser extends PatternParser {

 public ExPatternParser(String pattern) {
  super(pattern);
 }

 /**
  * 重寫finalizeConverter,對特定的佔位符進行處理,T表示執行緒ID佔位符
  */
 @Override
 protected void finalizeConverter(char c) {
  if (c == 'T') {
   this.addConverter(new ExPatternConverter(this.formattingInfo));
  } else {
   super.finalizeConverter(c);
  }
 }

 private static class ExPatternConverter extends PatternConverter {

  public ExPatternConverter(FormattingInfo fi) {
   super(fi);
  }

  /**
   * 當需要顯示執行緒ID的時候,返回當前呼叫執行緒的ID
   */
  @Override
  protected String convert(LoggingEvent event) {
   return String.valueOf(Thread.currentThread().getId());
  }

 }
}

到此已經擴充套件完成,將以上內容編譯後(可以打成jar包)和log4j.jar一同使用(使用同一個類裝載器裝載),然後配置log4j.properties類,修改

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout


log4j.appender.stdout.layout=ex.log4j.ExPatternLayout

在輸出格式中增加%T(log4j定義%t表示執行緒名稱,%T沒有定義,所以這裡使用%T表示執行緒ID),

log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %T %c %5p - %m%n

下面就按照平常的習慣使用log4j即可,再輸出的日誌中就會包含執行緒ID,例如:

2009-03-29 10:43:58 1 test.log.Log4jTest  INFO - ok

時間後面的'1'就表示執行緒id,當在多執行緒環境下,例如web環境,用這種方式就能很容易區分出一次web請求過程中打印出的日誌資訊,而不會和其他web請求打印出的日誌資訊混淆。這樣即增加的日誌的可讀性,也不會輸出太多的無用資訊。