自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java日志緩存機(jī)制的實(shí)現(xiàn)

開(kāi)發(fā) 后端
Java 日志機(jī)制在很多文章中都有介紹,為了便于后面文章部分的理解,在這里再簡(jiǎn)單介紹一下本文用到的一些關(guān)鍵字。

概述

日志技術(shù)為產(chǎn)品的質(zhì)量和服務(wù)提供了重要的支撐。JDK 在 1.4 版本以后加入了日志機(jī)制,為 Java 開(kāi)發(fā)人員提供了便利。但這種日志機(jī)制是基于靜態(tài)日志級(jí)別的,也就是在程序運(yùn)行前就需設(shè)定下來(lái)要打印的日志級(jí)別,這樣就會(huì)帶來(lái)一些不便。

在 JDK 提供的日志功能中,日志級(jí)別被細(xì)化為 9 級(jí),用以區(qū)分不同日志的用途,用來(lái)記錄一個(gè)錯(cuò)誤,或者記錄正常運(yùn)行的信息,又或是記錄詳細(xì)的調(diào)試信息。由于日志級(jí)別是靜態(tài)的,如果日志級(jí)別設(shè)定過(guò)高,低級(jí) 別的日志難以打印出來(lái),從而導(dǎo)致在錯(cuò)誤發(fā)生時(shí)候,難以去追蹤錯(cuò)誤的發(fā)生原因,目前常常采用的方式是在錯(cuò)誤發(fā)生的時(shí)候,不得不先調(diào)整日志級(jí)別到相對(duì)低的程 度,然后再去觸發(fā)錯(cuò)誤,使得問(wèn)題根源得到顯現(xiàn)。但是這種發(fā)生問(wèn)題需要改動(dòng)產(chǎn)品配置,然后重新觸發(fā)問(wèn)題進(jìn)行調(diào)試的方式使得產(chǎn)品用戶(hù)體驗(yàn)變差,而且有些問(wèn)題會(huì) 因?yàn)榕及l(fā)性,環(huán)境很復(fù)雜等原因很難重新觸發(fā)。

相反,如果起初就把日志級(jí)別調(diào)整到比較低,那么日志中間會(huì)有大量無(wú)用信息,而且當(dāng)產(chǎn)品比較復(fù)雜的時(shí)候,會(huì)導(dǎo)致產(chǎn)生的日志文件很大,刷新很快,無(wú)法及時(shí)的記錄有效的信息,甚至成為性能瓶頸,從而降低了日志功能對(duì)產(chǎn)品的幫助。

本文借助 Java Logging 中的 MemoryHandler 類(lèi)將所有級(jí)別日志緩存起來(lái),在適當(dāng)時(shí)刻輸出,來(lái)解決這個(gè)問(wèn)題。主要圍繞 MemoryHandler 的定義和logging.properties 文件的處理而展開(kāi)。

實(shí)例依附的場(chǎng)景如下,設(shè)想用戶(hù)需要在產(chǎn)品發(fā)生嚴(yán)重錯(cuò)誤時(shí),查看先前發(fā)生的包含 Exception 的錯(cuò)誤信息,以此作為診斷問(wèn)題緣由的依據(jù)。使用 Java 緩沖機(jī)制作出的一個(gè)解決方案是,將所有產(chǎn)品運(yùn)行過(guò)程中產(chǎn)生的包含Exception 的日志條目保存在一個(gè)可設(shè)定大小的循環(huán)緩沖隊(duì)列中,當(dāng)嚴(yán)重錯(cuò)誤(SEVERE)發(fā)生時(shí),將緩沖隊(duì)列中的日志輸出到指定平臺(tái),供用戶(hù)查閱。

Java 日志機(jī)制的介紹

Java 日志機(jī)制在很多文章中都有介紹,為了便于后面文章部分的理解,在這里再簡(jiǎn)單介紹一下本文用到的一些關(guān)鍵字。

Level:JDK 中定義了 Off、Severe、Warning、Info、Config、Fine、Finer、Finest、All 九個(gè)日志級(jí)別,定義 Off 為日志***等級(jí),All 為***等級(jí)。每條日志必須對(duì)應(yīng)一個(gè)級(jí)別。級(jí)別的定義主要用來(lái)對(duì)日志的嚴(yán)重程度進(jìn)行分類(lèi),同時(shí)可以用于控制日志是否輸出。

LogRecord:每一條日志會(huì)被記錄為一條 LogRecord, 其中存儲(chǔ)了類(lèi)名、方法名、線(xiàn)程 ID、打印的消息等等一些信息。

Logger:日志結(jié)構(gòu)的基本單元。Logger 是以樹(shù)形結(jié)構(gòu)存儲(chǔ)在內(nèi)存中的,根節(jié)點(diǎn)為 root。com.test(如果存在)一定是 com.test.demo(如果存在)的父節(jié)點(diǎn),即前綴匹配的已存在的 logger 一定是這個(gè) logger 的父節(jié)點(diǎn)。這種父子關(guān)系的定義,可以為用戶(hù)提供更為自由的控制粒度。因?yàn)樽庸?jié)點(diǎn)中如果沒(méi)有定義處理規(guī)則,如級(jí)別 handler、formatter 等,那么默認(rèn)就會(huì)使用父節(jié)點(diǎn)中的這些處理規(guī)則。

Handler:用來(lái)處理 LogRecord,默認(rèn) Handler 是可以連接成一個(gè)鏈狀,依次對(duì) LogRecord 進(jìn)行處理。

Filter:日志過(guò)濾器。在 JDK 中,沒(méi)有實(shí)現(xiàn)。

Formatter:它主要用于定義一個(gè) LogRecord 的輸出格式。

圖 1. Java 日志處理流程

圖 1 展示了一個(gè) LogRecord 的處理流程。一條日志進(jìn)入處理流程首先是 Logger,其中定義了可通過(guò)的 Level,如果 LogRecord 的 Level 高于Logger 的等級(jí),則進(jìn)入 Filter(如果有)過(guò)濾。如果沒(méi)有定義 Level,則使用父 Logger 的 Level。Handler 中過(guò)程類(lèi)似,其中 Handler 也定義了可通過(guò) Level,然后進(jìn)行 Filter 過(guò)濾,通過(guò)如果后面還有其他 Handler,則直接交由后面的 Handler 進(jìn)行處理,否則會(huì)直接綁定到 formatter 上面輸出到指定位置。

在實(shí)現(xiàn)日志緩存之前,先對(duì) Filter 和 Formatter 兩個(gè)輔助類(lèi)進(jìn)行介紹。

Filter

Filter 是一個(gè)接口,主要是對(duì) LogRecord 進(jìn)行過(guò)濾,控制是否對(duì) LogRecord 進(jìn)行進(jìn)一步處理,其可以綁定在 Logger 下或 Handler 下。

只要在 boolean isLoggable(LogRecord)方法中加上過(guò)濾邏輯就可以實(shí)現(xiàn)對(duì) logrecord 進(jìn)行控制,如果只想對(duì)發(fā)生了 Exception 的那些 log 記錄進(jìn)行記錄,那么可以通過(guò)清單 1 來(lái)實(shí)現(xiàn),當(dāng)然首先需要將該 Filter 通過(guò)調(diào)用 setFilter(Filter)方法或者配置文件方式綁定到對(duì)應(yīng)的 Logger 或 Handler。

清單 1. 一個(gè) Filter 實(shí)例的實(shí)現(xiàn)

  1. Override 
  2.  public boolean isLoggable(LogRecord record){ 
  3.  if(record.getThrown()!=null){ 
  4.         return true
  5.  }else
  6.          return false;  
  7.  } 
  8.  } 

Formatter

Formatter 主要是對(duì) Handler 在輸出 log 記錄的格式進(jìn)行控制,比如輸出日期的格式,輸出為 HTML 還是 XML 格式,文本參數(shù)替換等。Formatter 可以綁定到 Handler 上,Handler 會(huì)自動(dòng)調(diào)用 Formatter 的 String format(LogRecord r) 方法對(duì)日志記錄進(jìn)行格式化,該方法具有默認(rèn)的實(shí)現(xiàn),如果想實(shí)現(xiàn)自定義格式可以繼承 Formater 類(lèi)并重寫(xiě)該方法,默認(rèn)情況下例如清單 2 在經(jīng)過(guò) Formatter 格式化后,會(huì)將 {0} 和 {1} 替換成對(duì)應(yīng)的參數(shù)。

清單 2. 記錄一條 log

  1. logger.log(Level.WARNING,"this log is for test1: {0} and test2:{1}"
  2.     new Object[]{newTest1(), 
  3.     new Test2()}); 

MemoryHandler

MemoryHandler 是 Java Logging 中兩大類(lèi) Handler 之一,另一類(lèi)是 StreamHandler,二者直接繼承于 Handler,代表了兩種不同的設(shè)計(jì)思路。Java Logging Handler 是一個(gè)抽象類(lèi),需要根據(jù)使用場(chǎng)景創(chuàng)建具體 Handler,實(shí)現(xiàn)各自的 publish、flush 以及 close 等方法。

MemoryHandler 使用了典型的“注冊(cè) – 通知”的觀(guān)察者模式。MemoryHandler 先注冊(cè)到對(duì)自己感興趣的 Logger 中(logger.addHandler(handler)),在這些 Logger 調(diào)用發(fā)布日志的 API:log()、logp()、logrb() 等,遍歷這些 Logger 下綁定的所有 Handlers 時(shí),通知觸發(fā)自身 publish(LogRecord)方法的調(diào)用,將日志寫(xiě)入 buffer,當(dāng)轉(zhuǎn)儲(chǔ)到下一個(gè)日志發(fā)布平臺(tái)的條件成立,轉(zhuǎn)儲(chǔ)日志并清空 buffer。

這里的 buffer 是 MemoryHandler 自身維護(hù)一個(gè)可自定義大小的循環(huán)緩沖隊(duì)列,來(lái)保存所有運(yùn)行時(shí)觸發(fā)的 Exception 日志條目。同時(shí)在構(gòu)造函數(shù)中要求指定一個(gè) Target Handler,用于承接輸出;在滿(mǎn)足特定 flush buffer 的條件下,如日志條目等級(jí)高于 MemoryHandler 設(shè)定的 push level 等級(jí)(實(shí)例中定義為 SEVERE)等,將日志移交至下一步輸出平臺(tái)。從而形成如下日志轉(zhuǎn)儲(chǔ)輸出鏈:

圖 2. Log 轉(zhuǎn)儲(chǔ)鏈

在實(shí)例中,通過(guò)對(duì) MemoryHandler 配置項(xiàng) .push 的 Level 進(jìn)行判斷,決定是否將日志推向下一個(gè) Handler,通常在 publish() 方法內(nèi)實(shí)現(xiàn)。代碼清單如下:

#p#

清單 3

  1. // 只紀(jì)錄有異常并且高于 pushLevel 的 logRecord 
  2.     final Level level = record.getLevel();        
  3.     final Throwable thrown = record.getThrown(); 
  4.     If(level >= pushLevel){ 
  5.        push(); 
  6.     } 

MemoryHandler.push 方法的觸發(fā)條件

Push 方法會(huì)導(dǎo)致 MemoryHandler 轉(zhuǎn)儲(chǔ)日志到下一 handler,清空 buffer。觸發(fā)條件可以是但不局限于以下幾種,實(shí)例中使用的是默認(rèn)的***種:

  • 日志條目的 Level 大于或等于當(dāng)前 MemoryHandler 中默認(rèn)定義或用戶(hù)配置的 pushLevel;
  • 外部程序調(diào)用 MemoryHandler 的 push 方法;
  • MemoryHandler 子類(lèi)可以重載 log 方法或自定義觸發(fā)方法,在方法中逐一掃描日志條目,滿(mǎn)足自定義規(guī)則則觸發(fā)轉(zhuǎn)儲(chǔ)日志和清空 buffer 的操作。MemoryHanadler 的可配置屬性

表 1.MemoryHandler 可配置屬性

屬性名 描述 缺省值
繼承屬性 MemoryHandler.level MemoryHandler 接受的輸入到 buffer 的日志等級(jí) Level.INFO
MemoryHandler.filter 在輸入到 buffer 之前,可在 filter 中自定義除日志等級(jí)外的其他過(guò)濾條件 (Undefined)
MemoryHandler.formatter 指定輸入至 buffer 的日志格式 (Undefined)
MemoryHandler.encoding 指定輸入至 buffer 的日志編碼,在 MemoryHandler 中應(yīng)用甚少 (Undefined)
私有屬性 MemoryHandler.size 以日志條目為單位定義循環(huán) buffer 的大小 1,000
MemoryHandler.push 定義將 buffer 中的日志條目發(fā)送至下一個(gè) Handler 的*** Level(包含) Level.SEVERE
MemoryHandler.target 在構(gòu)造函數(shù)中指定下一步承接日志的 Handler (Undefined)

使用方式:

以上是記錄產(chǎn)品 Exception 錯(cuò)誤日志,以及如何轉(zhuǎn)儲(chǔ)的 MemoryHandler 處理的內(nèi)部細(xì)節(jié);接下來(lái)給出 MemoryHandler 的一些使用方式。

1. 直接使用 java.util.logging 中的 MemoryHandler

清單4

  1. // 在 buffer 中維護(hù) 5 條日志信息 
  2. // 僅記錄 Level 大于等于 Warning 的日志條目并 
  3. // 刷新 buffer 中的日志條目到 fileHandler 中處理 
  4.          int bufferSize = 5
  5.          f = new FileHandler("testMemoryHandler.log"); 
  6.          m = new MemoryHandler(f, bufferSize, Level.WARNING); 
  7.          … 
  8.          myLogger = Logger.getLogger("com.ibm.test"); 
  9.          myLogger.addHandler(m); 
  10.          myLogger.log(Level.WARNING, “this is a WARNING log”); 

. 自定義

1)反射

思考自定義 MyHandler 繼承自 MemoryHandler 的場(chǎng)景,由于無(wú)法直接使用作為父類(lèi)私有屬性的 size、buffer 及 buffer 中的 cursor,如果在 MyHandler 中有獲取和改變這些屬性的需求,一個(gè)途徑是使用反射。清單 5 展示了使用反射讀取用戶(hù)配置并設(shè)置私有屬性。

清單5

  1. int m_size; 
  2.   String sizeString = manager.getProperty(loggerName + ".size"); 
  3.   if (null != sizeString) { 
  4.          try { 
  5.           m_size = Integer.parseInt(sizeString); 
  6.           if (m_size <= 0) { 
  7.              m_size = BUFFER_SIZE; // default 1000 
  8.           } 
  9.  // 通過(guò) java 反射機(jī)制獲取私有屬性 
  10.           Field f; 
  11.           f = getClass().getSuperclass().getDeclaredField("size"); 
  12.           f.setAccessible(true); 
  13.           f.setInt(this, m_size); 
  14.           f = getClass().getSuperclass().getDeclaredField("buffer"); 
  15.           f.setAccessible(true); 
  16.           f.set(thisnew LogRecord[m_size]); 
  17.          } catch (Exception e) { 
  18.          } 
  19.   } 

2)重寫(xiě)

直接使用反射方便快捷,適用于對(duì)父類(lèi)私有屬性無(wú)頻繁訪(fǎng)問(wèn)的場(chǎng)景。思考這樣一種場(chǎng)景,默認(rèn)環(huán)形隊(duì)列無(wú)法滿(mǎn)足我們存儲(chǔ)需求,此時(shí)不妨令自定義的 MyMemoryHandler 直接繼承 Handler,直接對(duì)存儲(chǔ)結(jié)構(gòu)進(jìn)行操作,可以通過(guò)清單 6 實(shí)現(xiàn)。

清單 6

  1. public class MyMemoryHandler extends Handler{ 
  2.   // 默認(rèn)存儲(chǔ) LogRecord 的緩沖區(qū)容量 
  3.   private static final int DEFAULT_SIZE = 1000
  4.   // 設(shè)置緩沖區(qū)大小 
  5.   private int size = DEFAULT_SIZE; 
  6.   // 設(shè)置緩沖區(qū) 
  7.   private LogRecord[] buffer; 
  8.   // 參考 java.util.logging.MemoryHandler 實(shí)現(xiàn)其它部分 
  9.   ... 
  10.  } 

使用 MemoryHandler 時(shí)需關(guān)注的幾個(gè)問(wèn)題

了解了使用 MemoryHandler 實(shí)現(xiàn)的 Java 日志緩沖機(jī)制的內(nèi)部細(xì)節(jié)和外部應(yīng)用之后,來(lái)著眼于兩處具體實(shí)現(xiàn)過(guò)程中遇到的問(wèn)題:Logger/Handler/LogRecord Level 的傳遞影響,以及如何在開(kāi)發(fā) MemoryHandler 過(guò)程中處理錯(cuò)誤日志。

1. Level 的傳遞影響

Java.util.logging 中有三種類(lèi)型的 Level,分別是 Logger 的 Level,Handler 的 Level 和 LogRecord 的 Level. 前兩者可以通過(guò)配置文件設(shè)置。之后將日志的 Level 分別與 Logger 和 Handler 的 Level 進(jìn)行比較,過(guò)濾無(wú)須記錄的日志。在使用 Java Log 時(shí)需關(guān)注 Level 之間相互影響的問(wèn)題,尤其在遍歷 Logger 綁定了多個(gè) Handlers 時(shí)。如圖 3 所示:

圖 3. Java Log 中 Level 的傳遞影響

Java.util.logging.Logger 提供的 setUseParentHandlers 方法,也可能會(huì)影響到最終輸出終端的日志顯示。這個(gè)方法允許用戶(hù)將自身的日志條目打印一份到 Parent Logger 的輸出終端中。缺省會(huì)打印到 Parent Logger 終端。此時(shí),如果 Parent Logger Level 相關(guān)的設(shè)置與自身 Logger 不同,則打印到 Parent Logger 和自身中的日志條目也會(huì)有所不同。如圖 4 所示:

圖 4. 子類(lèi)日志需打印到父類(lèi)輸出終端

2. 開(kāi)發(fā) log 接口過(guò)程中處理錯(cuò)誤日志

在開(kāi)發(fā) log 相關(guān)接口中調(diào)用自身接口打印 log,可能會(huì)陷入無(wú)限循環(huán)。Java.util.logging 中考慮到這類(lèi)問(wèn)題,提供了一個(gè) ErrorManager 接口,供 Handler 在記錄日志期間報(bào)告任何錯(cuò)誤,而非直接拋出異?;蛘{(diào)用自身的 log 相關(guān)接口記錄錯(cuò)誤或異常。Handler 需實(shí)現(xiàn) setErrorManager() 方法,該方法為此應(yīng)用程序構(gòu)造 java.util.logging.ErrorManager 對(duì)象,并在錯(cuò)誤發(fā)生時(shí),通過(guò) reportError 方法調(diào)用 ErrorManager 的 error 方法,缺省將錯(cuò)誤輸出到標(biāo)準(zhǔn)錯(cuò)誤流,或依據(jù) Handler 中自定義的實(shí)現(xiàn)處理錯(cuò)誤流。關(guān)閉錯(cuò)誤流時(shí),使用 Logger.removeHandler 移除此 Handler 實(shí)例。

兩種經(jīng)典使用場(chǎng)景,一種是自定義 MyErrorManager,實(shí)現(xiàn)父類(lèi)相關(guān)接口,在記錄日志的程序中調(diào)用 MyHandler.setErrorManager(new MyEroorManager()); 另一種是在 Handler 中自定義 ErrorManager 相關(guān)方法,示例如清單 7:

#p#

清單 7

  1. public class MyHandler extends Handler{ 
  2. // 在構(gòu)造方法中實(shí)現(xiàn) setErrorManager 方法 
  3. public MyHandler(){ 
  4.    ...... 
  5.     setErrorManager (new ErrorManager() { 
  6.         public void  error (String msg, Exception ex, int code) { 
  7.             System.err.println("Error reported by MyHandler " 
  8.                              + msg + ex.getMessage()); 
  9.         } 
  10.     }); 
  11. public void publish(LogRecord record){ 
  12.     if (!isLoggable(record)) return
  13.     try { 
  14.         // 一些可能會(huì)拋出異常的操作 
  15.     } catch(Exception e) { 
  16.         reportError ("Error occurs in publish ", e, ErrorManager.WRITE_FAILURE); 
  17.     } 
  18. ...... 

logging.properties

logging.properties 文件是 Java 日志的配置文件,每一行以“key=value”的形式描述,可以配置日志的全局信息和特定日志配置信息,清單 8 是我們?yōu)闇y(cè)試代碼配置的 logging.properties。

清單 8. logging.properties 文件示例

  1. #Level 等級(jí) OFF > SEVERE > WARNING > INFO > CONFIG > FINE > FINER > FINEST > ALL 
  2. # 為 FileHandler 指定日志級(jí)別 
  3. java.util.logging.FileHandler.level=WARNING 
  4. # 為 FileHandler 指定 formatter 
  5. java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter 
  6. # 為自定義的 TestMemoryHandler 指定日志級(jí)別 
  7. com.ibm.test.MemoryHandler.level=INFO 
  8. # 設(shè)置 TestMemoryHandler 最多記錄日志條數(shù) 
  9. com.ibm.test.TestMemoryHandler.size=1000 
  10. # 設(shè)置 TestMemoryHandler 的自定義域 useParentLevel 
  11. com.ibm.test.TestMemoryHandler.useParentLevel=WARNING 
  12. # 設(shè)置特定 log 的 handler 為 TestMemoryHandler 
  13. com.ibm.test.handlers=com.ibm.test.TestMemoryHandler 
  14. # 指定全局的 Handler 為 FileHandler 
  15. handlers=java.util.logging.FileHandler 

從 清單 8 中可以看出 logging.properties 文件主要是用來(lái)給 logger 指定等級(jí)(level),配置 handler 和 formatter 信息。

如何監(jiān)聽(tīng) logging.properties

如果一個(gè)系統(tǒng)對(duì)安全性要求比較高,例如系統(tǒng)需要對(duì)更改 logging.properties 文件進(jìn)行日志記錄,記錄何時(shí)何人更改了哪些記錄,那么應(yīng)該怎么做呢?

這里可以利用 JDK 提供的 PropertyChangeListener 來(lái)監(jiān)聽(tīng) logging.properties 文件屬性的改變。

例如創(chuàng)建一個(gè) LogPropertyListener 類(lèi),其實(shí)現(xiàn)了 java.benas.PropertyChangeListener 接口,PropertyChangeListener 接口中只包含一個(gè) propertyChange(PropertyChangeEvent)方法,該方法的實(shí)現(xiàn)如清 9 所示。

清單 9. propertyChange 方法的實(shí)現(xiàn)

  1. @Override 
  2. public void propertyChange(PropertyChangeEvent event) { 
  3.    if (event.getSource() instanceof LogManager){ 
  4.        LogManager manager=(LogManager)event.getSource(); 
  5.        update(manager); 
  6.        execute(); 
  7.        reset(); 
  8.    } 

propertyChange(PropertyChangeEvent)方法中首先調(diào)用 update(LogManager)方法來(lái)找出 logging.properties 文件中更改的,增加的以及刪除的項(xiàng),這部分代碼如清單 10 所示;然后調(diào)用 execute() 方法來(lái)執(zhí)行具體邏輯,參見(jiàn) 清單 11;***調(diào)用 reset() 方法對(duì)相關(guān)屬性保存以及清空,如 清單 12 所示。

清單 10. 監(jiān)聽(tīng)改變的條目

  1. public void update(LogManager manager){ 
  2.  Properties logProps = null ; 
  3.   // 使用 Java 反射機(jī)制獲取私有屬性 
  4.    try { 
  5.      Field f = manager.getClass().getDeclaredField("props"); 
  6.      f.setAccessible(true ); 
  7.      logProps=(Properties)f.get(manager); 
  8.     }catch (Exception e){ 
  9.        logger.log(Level.SEVERE,"Get private field error.", e); 
  10.         return ; 
  11.    } 
  12.    Set<String> logPropsName=logProps.stringPropertyNames(); 
  13.     for (String logPropName:logPropsName){ 
  14.         String newVal=logProps.getProperty(logPropName).trim(); 
  15.        // 記錄當(dāng)前的屬性 
  16.        newProps.put(logPropName, newVal);   
  17.        // 如果給屬性上次已經(jīng)記錄過(guò) 
  18.        if (oldProps.containsKey(logPropName)){ 
  19.             String oldVal = oldProps.get(logPropName); 
  20.             if (newVal== null ?oldVal== null :newVal.equals(oldVal)){ 
  21.            // 屬性值沒(méi)有改變,不做任何操作 
  22.         }else { 
  23.             changedProps.put(logPropName, newVal); 
  24.        } 
  25.        oldProps.remove(logPropName); 
  26.    }else {// 如果上次沒(méi)有記錄過(guò)該屬性,則其應(yīng)為新加的屬性,記錄之 
  27.         changedProps.put(logPropName, newVal);               
  28.        } 
  29.     } 

代碼中 oldProps、newProps 以及 changedProps 都是 HashMap類(lèi)型,oldProps 存儲(chǔ)修改前 logging.properties 文件內(nèi)容,newProps 存儲(chǔ)修改后 logging.properties 內(nèi)容,changedProps 主要用來(lái)存儲(chǔ)增加的或者是修改的部分。

方法首先通過(guò) Java 的反射機(jī)制獲得 LogManager 中的私有屬性 props(存儲(chǔ)了 logging.properties 文件中的屬性信息),然后通過(guò)與 oldProps 比較可以得到增加的以及修改的屬性信息,*** oldProps 中剩下的就是刪除的信息了。

#p#

清單 11. 具體處理邏輯方法

  1. private void execute(){ 
  2.  // 處理刪除的屬性 
  3.  for (String prop:oldProps.keySet()){ 
  4.    // 這里可以加入其它處理步驟 
  5.    logger.info("'"+prop+"="+oldProps.get(prop)+"'has been removed");           
  6.  } 
  7.  // 處理改變或者新加的屬性 
  8.  for (String prop:changedProps.keySet()){ 
  9.      // 這里可以加入其它處理步驟 
  10.      logger.info("'"+prop+"="+oldProps.get(prop)+"'has been changed or added"); 
  11.  } 

該方法是主要的處理邏輯,對(duì)修改或者刪除的屬性進(jìn)行相應(yīng)的處理,比如記錄屬性更改日志等。這里也可以獲取當(dāng)前系統(tǒng)的登錄者,和當(dāng)前時(shí)間,這樣便可以詳細(xì)記錄何人何時(shí)更改過(guò)哪個(gè)日志條目。

清單 12. 重置所有數(shù)據(jù)結(jié)構(gòu)

  1. private void reset(){ 
  2. oldProps = newProps; 
  3. newProps= new HashMap< String,String>(); 
  4. changedProps.clear(); 

eset() 方法主要是用來(lái)重置各個(gè)屬性,以便下一次使用。

當(dāng)然如果只寫(xiě)一個(gè) PropertyChangeListener 還不能發(fā)揮應(yīng)有的功能,還需要將這個(gè) PropertyChangeListener 實(shí)例注冊(cè)到 LogManager 中,可以通過(guò)清單 13 實(shí)現(xiàn)。

清單 13. 注冊(cè) PropertyChangeListener

  1. // 為'logging.properties'文件注冊(cè)監(jiān)聽(tīng)器 
  2. LogPropertyListener listener= new LogPropertyListener(); 
  3. LogManager.getLogManager().addPropertyChangeListener(listener); 

如何實(shí)現(xiàn)自定義標(biāo)簽

在 清單 8中有一些自定義的條目,比如 com.ibm.test.TestMemoryHandler。

useParentLever=WARNING”,表示如果日志等級(jí)超過(guò) useParentLever 所定義的等級(jí) WARNING 時(shí),該條日志在 TestMemoryHandler 處理后需要傳遞到對(duì)應(yīng) Log 的父 Log 的 Handler 進(jìn)行處理(例如將發(fā)生了 WARNING 及以上等級(jí)的日志上下文緩存信息打印到文件中),否則不傳遞到父 Log 的 Handler 進(jìn)行處理,這種情況下如果不做任何處理,Java 原有的 Log 機(jī)制是不支持這種定義的。那么如何使得 Java Log 支持這種自定義標(biāo)簽?zāi)??這里可以使用 PropertyListener 對(duì)自定義標(biāo)簽進(jìn)行處理來(lái)使得 Java Log 支持這種自定義標(biāo)簽,例如對(duì)“useParentLever”進(jìn)行處理可以通過(guò)清單 14 實(shí)現(xiàn)。

清單 14

  1. private void execute(){ 
  2.        // 處理刪除的屬性 
  3.         for (String prop:oldProps.keySet()){ 
  4.             if (prop.endsWith(".useParentLevel")){ 
  5.                String logName=prop.substring(0, prop.lastIndexOf(".")); 
  6.                Logger log=Logger.getLogger(logName); 
  7.                 for (Handler handler:log.getHandlers()){ 
  8.                     if (handler  instanceof TestMemoryHandler){ 
  9.                        ((TestMemoryHandler)handler) 
  10.                            .setUseParentLevel(oldProps.get(prop)); 
  11.                         break ; 
  12.                    } 
  13.                } 
  14.            } 
  15.        } 
  16.        // 處理改變或者新加的屬性 
  17.         for (String prop:changedProps.keySet()){ 
  18.             if (prop.endsWith(".useParentLevel")){ 
  19.                // 在這里添加邏輯處理步驟 
  20.            } 
  21.        } 

在清單 14 處理之后,就可以在自定義的 TestMemoryHandler 中進(jìn)行判斷了,對(duì) log 的等級(jí)與其域 useParentLevel 進(jìn)行比較,決定是否傳遞到父 Log 的 Handler 進(jìn)行處理。在自定義 TestMemoryHandler 中保存對(duì)應(yīng)的 Log 信息可以很容易的實(shí)現(xiàn)將信息傳遞到父 Log 的 Handler,而保存對(duì)應(yīng) Log 信息又可以通過(guò) PropertyListener 來(lái)實(shí)現(xiàn),例如清單 15 更改了 清單 13中相應(yīng)代碼實(shí)現(xiàn)這一功能。

清單 15

  1. if (handler  instanceof TestMemoryHandler){ 
  2.     ((TestMemoryHandler)handler).setUseParentLevel(oldProps.get(prop)); 
  3.     ((TestMemoryHandler)handler).addLogger(log); 
  4.       break ; 

具體如何處理自定義標(biāo)簽的值那就看程序的需要了,通過(guò)這種方法就可以很容易在 logging.properties 添加自定義的標(biāo)簽了。

自定義讀取配置文件

如果 logging.properties 文件更改了,需要通過(guò)調(diào)用 readConfiguration(InputStream)方法使更改生效,但是從 JDK 的源碼中可以看到 readConfiguration(InputStream)方法會(huì)重置整個(gè) Log 系統(tǒng),也就是說(shuō)會(huì)把所有的 log 的等級(jí)恢復(fù)為默認(rèn)值,將所有 log 的 handler 置為 null 等,這樣所有存儲(chǔ)的信息就會(huì)丟失。

比如,TestMemoryHandler 緩存了 1000 條 logRecord,現(xiàn)在用戶(hù)更改了 logging.properties 文件,并且調(diào)用了 readConfiguration(InputStream) 方法來(lái)使之生效,那么由于 JDK 本身的 Log 機(jī)制,更改后對(duì)應(yīng) log 的 TestMemoryHandler 就是新創(chuàng)建的,那么原來(lái)存儲(chǔ)的 1000 條 logRecord 的 TestMemoryHandler 實(shí)例就會(huì)丟失。

那么這個(gè)問(wèn)題應(yīng)該如何解決呢?這里給出三種思路:

1). 由于每個(gè) Handler 都有一個(gè) close() 方法(任何繼承于 Handler 的類(lèi)都需要實(shí)現(xiàn)該方法),Java Log 機(jī)制在將 handler 置為 null 之前會(huì)調(diào)用對(duì)應(yīng) handler 的 close() 方法,那么就可以在 handler(例如 TestMemoryHandler)的 close() 方法中保存下相應(yīng)的信息。

2). 研究 readConfiguration(InputStream)方法,寫(xiě)一個(gè)替代的方法,然后每次調(diào)用替代的方法。

3). 繼承 LogManager 類(lèi),覆蓋 readConfiguration(InputStream)方法。

這里***種方法是保存原有的信息,然后進(jìn)行恢復(fù),但是這種方法不是很實(shí)用和高效;第二和第三種方法其實(shí)是一樣的,都是寫(xiě)一個(gè)替代的方法,例如可以在 替代的方法中對(duì) Handler 為 TestMemoryHandler 的不置為 null,然后在讀取 logging.properties 文件時(shí)發(fā)現(xiàn)為 TestMemoryHandler 屬性時(shí),找到對(duì)應(yīng) TestMemoryHandler 的實(shí)例,并更改相應(yīng)的屬性值(這個(gè)在清單 14 中有所體現(xiàn)),其他不屬于 TestMemoryHandler 屬性值的可以按照 JDK 原有的處理邏輯進(jìn)行處理,比如設(shè)置 log 的 level 等。

另一方面,由于 JDK1.6 及之前版本不支持文件修改監(jiān)聽(tīng)功能,每次修改了 logging.properties 文件后需要顯式調(diào)用 readConfiguration(InputStream)才能使得修改生效,但是自 JDK1.7 開(kāi)始已經(jīng)支持對(duì)文件修改監(jiān)聽(tīng)功能了,主要是在 java.nio.file.* 包中提供了相關(guān)的 API,這里不再詳述。

那么在 JDK1.7 之前,可以使用 apache 的 commons-io 庫(kù)中的 FileMonitor 類(lèi),在此也不再詳述。

總結(jié)

通過(guò)對(duì) MemoryHandler 和 logging.properties 進(jìn)行定義,可以通過(guò) Java 日志實(shí)現(xiàn)自定義日志緩存,從而提高 Java 日志的可用性,為產(chǎn)品質(zhì)量提供更強(qiáng)有力的支持。

原文鏈接:http://blog.jobbole.com/44668/

責(zé)任編輯:陳四芳 來(lái)源: 伯樂(lè)在線(xiàn)
相關(guān)推薦

2011-12-15 09:33:19

Java

2014-11-04 10:34:27

JavaCache

2025-02-05 12:22:21

2019-11-14 14:30:10

Java類(lèi)反射代碼

2009-06-18 14:51:12

Hibernate緩存Hibernate

2009-11-23 17:56:44

PHP緩存機(jī)制

2011-03-16 09:26:41

ReadWriteLoJava

2015-09-28 15:59:00

Java動(dòng)態(tài)代理機(jī)制

2009-06-17 15:43:03

Hibernate緩存

2023-02-24 16:46:25

Glide緩存機(jī)制

2018-07-12 15:30:03

HTTP緩存機(jī)制

2024-06-28 08:31:54

2019-05-27 14:40:43

Java同步機(jī)制多線(xiàn)程編程

2009-11-09 17:55:13

WCF緩存

2016-03-09 09:54:47

Python開(kāi)發(fā)緩存機(jī)制

2010-10-13 16:44:10

MySQL查詢(xún)緩存機(jī)制

2025-01-02 14:50:34

MyBatis開(kāi)發(fā)緩存

2022-09-23 08:02:42

Kafka消息緩存

2010-09-14 09:30:04

Java多態(tài)

2018-08-07 10:44:50

緩存技術(shù)瀏覽器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)