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

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) { /* 實際輸出邏輯:檔案/網路/資料庫 */ }
}
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();
}
}
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
}
Handler fh = new FileHandler("%t/wombat.log");
Logger.getLogger("logname").addHandler(fh);
Logger.getLogger("com.wombat").setLevel(Level.FINEST);
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
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 等
}
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) {}
}
}
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();
}
}
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");
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、資料庫等後端。