本篇內(nèi)容介紹了“Java如何實(shí)現(xiàn)日志緩存機(jī)制”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)公司專注于佛坪網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供佛坪營(yíng)銷型網(wǎng)站建設(shè),佛坪網(wǎng)站制作、佛坪網(wǎng)頁(yè)設(shè)計(jì)、佛坪網(wǎng)站官網(wǎng)定制、成都小程序開(kāi)發(fā)服務(wù),打造佛坪網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供佛坪網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
Java 日志機(jī)制的介紹
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)行分類,同時(shí)可以用于控制日志是否輸出。
LogRecord:每一條日志會(huì)被記錄為一條 LogRecord, 其中存儲(chǔ)了類名、方法名、線程 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)系的定義,可以為用戶提供更為自由的控制粒度。因?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ò)程類似,其中 Handler 也定義了可通過(guò) Level,然后進(jìn)行 Filter 過(guò)濾,通過(guò)如果后面還有其他 Handler,則直接交由后面的 Handler 進(jìn)行處理,否則會(huì)直接綁定到 formatter 上面輸出到指定位置。
在實(shí)現(xiàn)日志緩存之前,先對(duì) Filter 和 Formatter 兩個(gè)輔助類進(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)
Override public boolean isLoggable(LogRecord record){ if(record.getThrown()!=null){ return true; }else{ return false; } }
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 類并重寫該方法,默認(rèn)情況下例如清單 2 在經(jīng)過(guò) Formatter 格式化后,會(huì)將 {0} 和 {1} 替換成對(duì)應(yīng)的參數(shù)。
清單 2. 記錄一條 log
logger.log(Level.WARNING,"this log is for test1: {0} and test2:{1}", new Object[]{newTest1(), new Test2()});
MemoryHandler
MemoryHandler 是 Java Logging 中兩大類 Handler 之一,另一類是 StreamHandler,二者直接繼承于 Handler,代表了兩種不同的設(shè)計(jì)思路。Java Logging Handler 是一個(gè)抽象類,需要根據(jù)使用場(chǎng)景創(chuàng)建具體 Handler,實(shí)現(xiàn)各自的 publish、flush 以及 close 等方法。
MemoryHandler 使用了典型的“注冊(cè) – 通知”的觀察者模式。MemoryHandler 先注冊(cè)到對(duì)自己感興趣的 Logger 中(logger.addHandler(handler)),在這些 Logger 調(diào)用發(fā)布日志的 API:log()、logp()、logrb() 等,遍歷這些 Logger 下綁定的所有 Handlers 時(shí),通知觸發(fā)自身 publish(LogRecord)方法的調(diào)用,將日志寫入 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,用于承接輸出;在滿足特定 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)。代碼清單如下:
清單 3
// 只紀(jì)錄有異常并且高于 pushLevel 的 logRecord final Level level = record.getLevel(); final Throwable thrown = record.getThrown(); If(level >= pushLevel){ push(); }
MemoryHandler.push 方法的觸發(fā)條件
Push 方法會(huì)導(dǎo)致 MemoryHandler 轉(zhuǎn)儲(chǔ)日志到下一 handler,清空 buffer。觸發(fā)條件可以是但不局限于以下幾種,實(shí)例中使用的是默認(rèn)的***種:
日志條目的 Level 大于或等于當(dāng)前 MemoryHandler 中默認(rèn)定義或用戶配置的 pushLevel;
外部程序調(diào)用 MemoryHandler 的 push 方法;
MemoryHandler 子類可以重載 log 方法或自定義觸發(fā)方法,在方法中逐一掃描日志條目,滿足自定義規(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
// 在 buffer 中維護(hù) 5 條日志信息 // 僅記錄 Level 大于等于 Warning 的日志條目并 // 刷新 buffer 中的日志條目到 fileHandler 中處理 int bufferSize = 5; f = new FileHandler("testMemoryHandler.log"); m = new MemoryHandler(f, bufferSize, Level.WARNING); … myLogger = Logger.getLogger("com.ibm.test"); myLogger.addHandler(m); myLogger.log(Level.WARNING, “this is a WARNING log”);
. 自定義
1)反射
思考自定義 MyHandler 繼承自 MemoryHandler 的場(chǎng)景,由于無(wú)法直接使用作為父類私有屬性的 size、buffer 及 buffer 中的 cursor,如果在 MyHandler 中有獲取和改變這些屬性的需求,一個(gè)途徑是使用反射。清單 5 展示了使用反射讀取用戶配置并設(shè)置私有屬性。
清單5
int m_size; String sizeString = manager.getProperty(loggerName + ".size"); if (null != sizeString) { try { m_size = Integer.parseInt(sizeString); if (m_size <= 0) { m_size = BUFFER_SIZE; // default 1000 } // 通過(guò) java 反射機(jī)制獲取私有屬性 Field f; f = getClass().getSuperclass().getDeclaredField("size"); f.setAccessible(true); f.setInt(this, m_size); f = getClass().getSuperclass().getDeclaredField("buffer"); f.setAccessible(true); f.set(this, new LogRecord[m_size]); } catch (Exception e) { } }
2)重寫
直接使用反射方便快捷,適用于對(duì)父類私有屬性無(wú)頻繁訪問(wèn)的場(chǎng)景。思考這樣一種場(chǎng)景,默認(rèn)環(huán)形隊(duì)列無(wú)法滿足我們存儲(chǔ)需求,此時(shí)不妨令自定義的 MyMemoryHandler 直接繼承 Handler,直接對(duì)存儲(chǔ)結(jié)構(gòu)進(jìn)行操作,可以通過(guò)清單 6 實(shí)現(xiàn)。
清單 6
public class MyMemoryHandler extends Handler{ // 默認(rèn)存儲(chǔ) LogRecord 的緩沖區(qū)容量 private static final int DEFAULT_SIZE = 1000; // 設(shè)置緩沖區(qū)大小 private int size = DEFAULT_SIZE; // 設(shè)置緩沖區(qū) private LogRecord[] buffer; // 參考 java.util.logging.MemoryHandler 實(shí)現(xiàn)其它部分 ... }
使用 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 中有三種類型的 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è)方法允許用戶將自身的日志條目打印一份到 Parent Logger 的輸出終端中。缺省會(huì)打印到 Parent Logger 終端。此時(shí),如果 Parent Logger Level 相關(guān)的設(shè)置與自身 Logger 不同,則打印到 Parent Logger 和自身中的日志條目也會(huì)有所不同。如圖 4 所示:
圖 4. 子類日志需打印到父類輸出終端
2. 開(kāi)發(fā) log 接口過(guò)程中處理錯(cuò)誤日志
在開(kāi)發(fā) log 相關(guān)接口中調(diào)用自身接口打印 log,可能會(huì)陷入無(wú)限循環(huán)。Java.util.logging 中考慮到這類問(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)父類相關(guān)接口,在記錄日志的程序中調(diào)用 MyHandler.setErrorManager(new MyEroorManager()); 另一種是在 Handler 中自定義 ErrorManager 相關(guān)方法,示例如清單 7:
清單 7
public class MyHandler extends Handler{ // 在構(gòu)造方法中實(shí)現(xiàn) setErrorManager 方法 public MyHandler(){ ...... setErrorManager (new ErrorManager() { public void error (String msg, Exception ex, int code) { System.err.println("Error reported by MyHandler " + msg + ex.getMessage()); } }); } public void publish(LogRecord record){ if (!isLoggable(record)) return; try { // 一些可能會(huì)拋出異常的操作 } catch(Exception e) { reportError ("Error occurs in publish ", e, ErrorManager.WRITE_FAILURE); } } ...... }
logging.properties 文件是 Java 日志的配置文件,每一行以“key=value”的形式描述,可以配置日志的全局信息和特定日志配置信息,清單 8 是我們?yōu)闇y(cè)試代碼配置的 logging.properties。
清單 8. logging.properties 文件示例
#Level 等級(jí) OFF > SEVERE > WARNING > INFO > CONFIG > FINE > FINER > FINEST > ALL # 為 FileHandler 指定日志級(jí)別 java.util.logging.FileHandler.level=WARNING # 為 FileHandler 指定 formatter java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter # 為自定義的 TestMemoryHandler 指定日志級(jí)別 com.ibm.test.MemoryHandler.level=INFO # 設(shè)置 TestMemoryHandler 最多記錄日志條數(shù) com.ibm.test.TestMemoryHandler.size=1000 # 設(shè)置 TestMemoryHandler 的自定義域 useParentLevel com.ibm.test.TestMemoryHandler.useParentLevel=WARNING # 設(shè)置特定 log 的 handler 為 TestMemoryHandler com.ibm.test.handlers=com.ibm.test.TestMemoryHandler # 指定全局的 Handler 為 FileHandler 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 類,其實(shí)現(xiàn)了 java.benas.PropertyChangeListener 接口,PropertyChangeListener 接口中只包含一個(gè) propertyChange(PropertyChangeEvent)方法,該方法的實(shí)現(xiàn)如清 9 所示。
清單 9. propertyChange 方法的實(shí)現(xiàn)
@Override public void propertyChange(PropertyChangeEvent event) { if (event.getSource() instanceof LogManager){ LogManager manager=(LogManager)event.getSource(); update(manager); execute(); reset(); } }
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)改變的條目
public void update(LogManager manager){ Properties logProps = null ; // 使用 Java 反射機(jī)制獲取私有屬性 try { Field f = manager.getClass().getDeclaredField("props"); f.setAccessible(true ); logProps=(Properties)f.get(manager); }catch (Exception e){ logger.log(Level.SEVERE,"Get private field error.", e); return ; } SetlogPropsName=logProps.stringPropertyNames(); for (String logPropName:logPropsName){ String newVal=logProps.getProperty(logPropName).trim(); // 記錄當(dāng)前的屬性 newProps.put(logPropName, newVal); // 如果給屬性上次已經(jīng)記錄過(guò) if (oldProps.containsKey(logPropName)){ String oldVal = oldProps.get(logPropName); if (newVal== null ?oldVal== null :newVal.equals(oldVal)){ // 屬性值沒(méi)有改變,不做任何操作 }else { changedProps.put(logPropName, newVal); } oldProps.remove(logPropName); }else {// 如果上次沒(méi)有記錄過(guò)該屬性,則其應(yīng)為新加的屬性,記錄之 changedProps.put(logPropName, newVal); } } }
代碼中 oldProps、newProps 以及 changedProps 都是 HashMap類型,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 中剩下的就是刪除的信息了。
清單 11. 具體處理邏輯方法
private void execute(){ // 處理刪除的屬性 for (String prop:oldProps.keySet()){ // 這里可以加入其它處理步驟 logger.info("'"+prop+"="+oldProps.get(prop)+"'has been removed"); } // 處理改變或者新加的屬性 for (String prop:changedProps.keySet()){ // 這里可以加入其它處理步驟 logger.info("'"+prop+"="+oldProps.get(prop)+"'has been changed or added"); } }
該方法是主要的處理邏輯,對(duì)修改或者刪除的屬性進(jìn)行相應(yīng)的處理,比如記錄屬性更改日志等。這里也可以獲取當(dāng)前系統(tǒng)的登錄者,和當(dāng)前時(shí)間,這樣便可以詳細(xì)記錄何人何時(shí)更改過(guò)哪個(gè)日志條目。
清單 12. 重置所有數(shù)據(jù)結(jié)構(gòu)
private void reset(){ oldProps = newProps; newProps= new HashMap< String,String>(); changedProps.clear(); }
eset() 方法主要是用來(lái)重置各個(gè)屬性,以便下一次使用。
當(dāng)然如果只寫一個(gè) PropertyChangeListener 還不能發(fā)揮應(yīng)有的功能,還需要將這個(gè) PropertyChangeListener 實(shí)例注冊(cè)到 LogManager 中,可以通過(guò)清單 13 實(shí)現(xiàn)。
清單 13. 注冊(cè) PropertyChangeListener
// 為'logging.properties'文件注冊(cè)監(jiān)聽(tīng)器 LogPropertyListener listener= new LogPropertyListener(); LogManager.getLogManager().addPropertyChangeListener(listener);
在 清單 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
private void execute(){ // 處理刪除的屬性 for (String prop:oldProps.keySet()){ if (prop.endsWith(".useParentLevel")){ String logName=prop.substring(0, prop.lastIndexOf(".")); Logger log=Logger.getLogger(logName); for (Handler handler:log.getHandlers()){ if (handler instanceof TestMemoryHandler){ ((TestMemoryHandler)handler) .setUseParentLevel(oldProps.get(prop)); break ; } } } } // 處理改變或者新加的屬性 for (String prop:changedProps.keySet()){ if (prop.endsWith(".useParentLevel")){ // 在這里添加邏輯處理步驟 } } }
在清單 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
if (handler instanceof TestMemoryHandler){ ((TestMemoryHandler)handler).setUseParentLevel(oldProps.get(prop)); ((TestMemoryHandler)handler).addLogger(log); 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)在用戶更改了 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 的類都需要實(shí)現(xiàn)該方法),Java Log 機(jī)制在將 handler 置為 null 之前會(huì)調(diào)用對(duì)應(yīng) handler 的 close() 方法,那么就可以在 handler(例如 TestMemoryHandler)的 close() 方法中保存下相應(yīng)的信息。
2). 研究 readConfiguration(InputStream)方法,寫一個(gè)替代的方法,然后每次調(diào)用替代的方法。
3). 繼承 LogManager 類,覆蓋 readConfiguration(InputStream)方法。
“Java如何實(shí)現(xiàn)日志緩存機(jī)制”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!