NDC ( Nested Diagnostic Context )和 MDC ( Mapped Diagnostic Context )是 log4j 種非常有用的兩個類,它們用於存儲應用程序的上下文信息( context infomation ),從而便於在 log 中使用這些上下文信息。
NDC的實現是用hashtable來存儲每個線程的stack信息,這個stack是每個線程可以設置當前線程的request的相關信息,然後當前線 程在處理過程中只要在log4j配置打印出%x的信息,那麼當前線程的整個stack信息就會在log4j打印日誌的時候也會都打印出來,這樣可以很好的 跟蹤當前request的用戶行爲功能。
MDC的實現是使用threadlocal來保存每個線程的Hashtable的類似map的信息,其他功能類似。
性能
在記錄一些日誌信息時,會一定程度地影響系統的運行效率,這時日誌工具是否高效就是一個關鍵。Log4J的首要設計目標就是高效,一些關鍵組件都重寫過很多次以不斷提高性能。根據Log4J項目小組的報告,在AMD Duron 800MHz + JDK1.3.1的環境下,Log4J判斷一條日誌語句是否需要輸出僅需要5納秒。實際的日誌語句執行的也非常快速,從使用SimpleLayout的21微秒(幾乎與System.out.println一樣快),到使用TTCCLayout的37微秒不等。
NDC的實現代碼:
public class NDC { static Hashtable ht = new Hashtable(); private static Stack getCurrentStack() { if (ht != null) { return (Stack) ht.get(Thread.currentThread()); } return null; } public static String pop() { Stack stack = getCurrentStack(); if(stack != null && !stack.isEmpty()) return ((DiagnosticContext) stack.pop()).message; else return ""; } public static String peek() { Stack stack = getCurrentStack(); if(stack != null && !stack.isEmpty()) return ((DiagnosticContext) stack.peek()).message; else return ""; } public static void push(String message) { Stack stack = getCurrentStack(); if(stack == null) { DiagnosticContext dc = new DiagnosticContext(message, null); stack = new Stack(); Thread key = Thread.currentThread(); ht.put(key, stack); stack.push(dc); } else if (stack.isEmpty()) { DiagnosticContext dc = new DiagnosticContext(message, null); stack.push(dc); } else { DiagnosticContext parent = (DiagnosticContext) stack.peek(); stack.push(new DiagnosticContext(message, parent)); } }
MDC的實現:
public class MDC { final static MDC mdc = new MDC(); static final int HT_SIZE = 7; boolean java1; Object tlm; private Method removeMethod; private MDC() { java1 = Loader.isJava1(); if(!java1) { tlm = new ThreadLocalMap(); } try { removeMethod = ThreadLocal.class.getMethod("remove", null); } catch (NoSuchMethodException e) { // don't do anything - java prior 1.5 } } */ static public void put(String key, Object o) { if (mdc != null) { mdc.put0(key, o); } } static public Object get(String key) { if (mdc != null) { return mdc.get0(key); } return null; } static public void remove(String key) { if (mdc != null) { mdc.remove0(key); } } public static Hashtable getContext() { if (mdc != null) { return mdc.getContext0(); } else { return null; } } public static void clear() { if (mdc != null) { mdc.clear0(); } } private void put0(String key, Object o) { if(java1 || tlm == null) { return; } else { Hashtable ht = (Hashtable) ((ThreadLocalMap)tlm).get(); if(ht == null) { ht = new Hashtable(HT_SIZE); ((ThreadLocalMap)tlm).set(ht); } ht.put(key, o); } } private Object get0(String key) { if(java1 || tlm == null) { return null; } else { Hashtable ht = (Hashtable) ((ThreadLocalMap)tlm).get(); if(ht != null && key != null) { return ht.get(key); } else { return null; } } } private void remove0(String key) { if(!java1 && tlm != null) { Hashtable ht = (Hashtable) ((ThreadLocalMap)tlm).get(); if(ht != null) { ht.remove(key); // clean up if this was the last key if (ht.isEmpty()) { clear0(); } } } } private Hashtable getContext0() { if(java1 || tlm == null) { return null; } else { return (Hashtable) ((ThreadLocalMap)tlm).get(); } } private void clear0() { if(!java1 && tlm != null) { Hashtable ht = (Hashtable) ((ThreadLocalMap)tlm).get(); if(ht != null) { ht.clear(); } if(removeMethod != null) { // java 1.3/1.4 does not have remove - will suffer from a memory leak try { removeMethod.invoke(tlm, null); } catch (IllegalAccessException e) { // should not happen } catch (InvocationTargetException e) { // should not happen } } } } }
日誌信息代表的含義:
%p: 輸出日誌信息優先級,即DEBUG,INFO,WARN,ERROR,FATAL, %d: 輸出日誌時間點的日期或時間,默認格式爲ISO8601,也可以在其後指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},輸出類似:2002年10月18日 22:10:28,921 %r: 輸出自應用啓動到輸出該log信息耗費的毫秒數 %c: 輸出日誌信息所屬的類目,通常就是所在類的全名 %t: 輸出產生該日誌事件的線程名 %l: 輸出日誌事件的發生位置,相當於%C.%M(%F:%L)的組合,包括類目名、發生的線程,以及在代碼中的行數。舉例:Testlog4.main (TestLog4.java:10) %x: 輸出和當前線程相關聯的NDC(嵌套診斷環境),尤其用到像Java servlets這樣的多客戶多線程的應用中。 %%: 輸出一個”%”字符 %F: 輸出日誌消息產生時所在的文件名稱 %L: 輸出代碼中的行號 %m: 輸出代碼中指定的消息,產生的日誌具體信息 %n: 輸出一個回車換行符,Windows平臺爲”\r\n”,Unix平臺爲”\n”輸出日誌信息換行