首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

擴展 JDK 日誌框架:自訂 Handler、Formatter、Level 並整合 STAF 自動化測試日誌

     編輯:Java 相關知識

JDK Logging 架構回顧

java.util.logging(JDK 1.4+)核心組件:

  • Logger:日誌記錄入口
  • Handler:輸出目的地(Console/File/Socket/Memory/Stream)
  • Filter:條件過濾
  • Formatter:訊息格式化(Text/XML/HTML)
  • Level:七大預設等級(SEVERE→FINEST)

JDK 日誌框架架構

三大擴展點實作指南

1. 自訂 Handler(繼承 java.util.logging.Handler)

public class MyHandler extends Handler {
    private boolean doneHeader = false;
    public MyHandler() {
        setLevel(Level.INFO);
        setFilter(null);
        setFormatter(new SimpleFormatter());
    }
    @Override public void publish(LogRecord record) {
        if (!isLoggable(record)) return;
        String msg = getFormatter().format(record);
        try {
            if (!doneHeader) { output(getFormatter().getHead(this)); doneHeader = true; }
            output(msg);
        } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); }
    }
    @Override public void flush() { /* 清空緩衝 */ }
    @Override public void close() throws SecurityException {
        if (!doneHeader) { output(getFormatter().getHead(this)); doneHeader = true; }
        output(getFormatter().getTail(this)); flush();
    }
    private void output(String message) { /* 實際輸出邏輯:檔案/網路/資料庫 */ }
}

2. 自訂 Formatter(繼承 java.util.logging.Formatter)

public class MyFormatter extends Formatter {
    private final String lineSeparator = System.getProperty("line.separator");
    @Override public String format(LogRecord record) {
        StringBuffer sb = new StringBuffer();
        sb.append(record.getLevel().getLocalizedName());
        sb.append(formatMessage(record));  // 支援 java.text 格式化
        sb.append(lineSeparator);
        if (record.getThrown() != null) {
            try {
                StringWriter sw = new StringWriter();
                record.getThrown().printStackTrace(new PrintWriter(sw));
                sb.append(sw.toString());
            } catch (Exception ignored) {}
        }
        return sb.toString();
    }
}

3. 自訂 Level(繼承 java.util.logging.Level)

public class MyLevel extends Level {
    protected MyLevel(String name, int value) { super(name, value); }
    public static final Level LEVEL1 = new MyLevel("LEVEL1", 123);
    // 權重值:SEVERE=1000, WARNING=900, INFO=800, CONFIG=700, FINE=500, FINER=400, FINEST=300
}

配置方式:程式碼 vs 屬性檔

動態程式碼配置

Handler fh = new FileHandler("%t/wombat.log");
Logger.getLogger("logname").addHandler(fh);
Logger.getLogger("com.wombat").setLevel(Level.FINEST);

屬性檔配置(logger.properties)

com.xyz.foo.handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
com.xyz.foo.level = INFO
java.util.logging.FileHandler.level = SEVERE
java.util.logging.ConsoleHandler.level = INFO

啟動參數:java -Djava.util.logging.config.file=logger.properties -cp . MainClass

實戰整合:JDK Logging + STAF 日誌服務

STAF(Software Testing Automation Framework)提供分散式日誌服務,Java API 核心:

public class STAFLog {
    public STAFLog(String logType, String logName, STAFHandle handle);
    public STAFResult log(int level, String msg);
    public static STAFResult log(STAFHandle handle, String logType, String logName, int level, String msg);
    // logType: MACHINE/USER 等
}

步驟 1:STAFHandler 封裝 STAF API

public class STAFHandler extends Handler {
    private String logName;
    private static STAFHandle stafHandle = null;
    
    public STAFHandler(String name) { configure(); logName = name; }
    public STAFHandler() { configure(); }
    
    private void configure() {
        if (stafHandle == null) {
            try { stafHandle = new STAFHandle("my application"); }
            catch (STAFException e) { reportError("registe staf handle error", e, ErrorManager.OPEN_FAILURE); }
        }
        LogManager manager = LogManager.getLogManager();
        String cname = getClass().getName();
        // 讀取屬性檔配置
        logName = manager.getProperty(cname + ".name") != null ? 
                  manager.getProperty(cname + ".name") : "demo.staflog";
        // Formatter
        String sformatter = manager.getProperty(cname + ".formatter");
        Formatter formatter = null;
        if (sformatter != null) try { formatter = (Formatter)Class.forName(sformatter).newInstance(); } catch (Exception ignored) {}
        setFormatter(formatter != null ? formatter : new STAFFormatter());
        // Level
        String sLevel = manager.getProperty(cname + ".level");
        Level level = null;
        if (sLevel != null) try { level = STAFLevel.parse(sLevel); } catch (Exception ignored) {}
        setLevel(level != null ? level : STAFLevel.DEBUG);
    }
    
    @Override public void publish(LogRecord record) {
        if (!isLoggable(record)) return;
        try {
            String msg = getFormatter().format(record);
            STAFLog.log(stafHandle, STAFLog.MACHINE, logName, record.getLevel().getName(), msg);
        } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); }
    }
    @Override public void flush() {}
    @Override public void close() throws SecurityException {
        if (stafHandle != null) try { stafHandle.unRegister(); } catch (STAFException ignored) {}
    }
}

步驟 2:STAFFormatter 純文字格式

public class STAFFormatter extends Formatter {
    private final String lineSeparator = System.getProperty("line.separator");
    @Override public String format(LogRecord record) {
        StringBuffer sb = new StringBuffer();
        sb.append(formatMessage(record)).append(lineSeparator);
        if (record.getThrown() != null) {
            try {
                StringWriter sw = new StringWriter();
                record.getThrown().printStackTrace(new PrintWriter(sw));
                sb.append(sw.toString());
            } catch (Exception ignored) {}
        }
        return sb.toString();
    }
}

步驟 3:STAFLevel 對應 STAF 原生日誌等級

public class STAFLevel extends Level {
    protected STAFLevel(String name, int value) { super(name, value); }
    protected STAFLevel(String name, int value, String rb) { super(name, value, rb); }
    public static final Level FATAL = new STAFLevel("FATAL", 980);
    public static final Level ERROR = new STAFLevel("ERROR", 980);
    public static final Level TRACE = new STAFLevel("TRACE", 790);
    public static final Level DEBUG = new STAFLevel("DEBUG", 690);
}

完整使用範例

程式碼配置方式

Logger logger = Logger.getLogger(STAFLoggerTest.class.getName());
logger.setUseParentHandlers(false);
logger.setLevel(STAFLevel.DEBUG);
STAFHandler stafHandler = new STAFHandler("staflogger");
stafHandler.setLevel(STAFLevel.DEBUG);
stafHandler.setFormatter(new STAFFormatter());
logger.addHandler(stafHandler);

logger.log(STAFLevel.DEBUG, "debug log");
logger.log(STAFLevel.FATAL, "fatal log");
logger.log(STAFLevel.ERROR, "error log");
logger.log(STAFLevel.TRACE, "trace log");

屬性檔配置方式(logger.properties)

demo.staflog.STAFLoggerTest.handlers= demo.staflog.STAFHandler
demo.staflog.STAFLoggerTest.level = DEBUG
demo.staflog.STAFLoggerTest.useParentHandlers = FALSE
demo.staflog.STAFHandler.name= staflogger
demo.staflog.STAFHandler.level = DEBUG
demo.staflog.STAFHandler.formatter = demo.staflog.STAFFormatter

程式碼簡化為純日誌呼叫,部署後修改屬性檔即可調整 Handler/Level/Formatter 組合,無需重新編譯。

驗證結果

$ staf local log query machine mymachine logname staflogger
Response
--------
Date-Time       Level   Message
----------------- ----- ----------
20081111-16:15:21 Debug   debug log
20081111-16:15:21 Fatal   fatal log
20081111-16:15:21 Error   error log
20081111-16:15:21 Trace   trace log

結語

JDK Logging 雖起步晚於 Log4j,但零依賴、標準化、擴充性強。掌握 Handler/Formatter/Level 三大擴展點與 屬性檔配置,即可滿足企業級日誌需求(多輸出目的地、結構化格式、分級過濾、動態重配置)。本文示範的 STAF 整合模式同樣適用於 Logstash、Syslog、資料庫等後端。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved