最近做Veracode Scan,爲了解決Improper Output Neutralization for Logs (CWE ID 117)問題,涉及到jdk自帶log的一些內容做部分記錄。
一、Logger
核心的java.util.logging.Logger類,用於輸出log。
// 常規的得到logger的方法
Logger logger = Logger.getLogger(LoggerDemo.class.getName());
System.out.println(logger);
Logger rootLogger = Logger.getLogger("");
System.out.println(rootLogger);
輸出:
java.util.logging.Logger@5ca881b5
java.util.logging.LogManager$RootLogger@24d46ca6
可以看到通過Logger
的靜態方法getLogger(String name)
得到Logger
。
- 帶參數的返回的是
java.util.logging.Logger
。 - 參數爲空字符串的時候返回的是
java.util.logging.LogManager$RootLogger
。
實際上所有的通過此法得到的Logger
都是由LogManager
統一管理的,也就是通過name
參數保證每個Logger的唯一性。所有創建的Logger
都帶有parent
成員變量指向同一個RootLogger
。當新建的Logger
綁定handlers
的時候可以通過調用父級Logger
的handler
進行輸出log
。
Handler
遍歷方式是從自己到父級所有Handler
:
public void log(LogRecord record) {
if (!isLoggable(record.getLevel())) {
return;
}
Filter theFilter = filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
// Post the LogRecord to all our Handlers, and then to
// our parents' handlers, all the way up the tree.
Logger logger = this;
while (logger != null) {
//獲得自己綁定的Handler
final Handler[] loggerHandlers = isSystemLogger
? logger.accessCheckedHandlers()
: logger.getHandlers();
//遍歷調用自己綁定的Handler
for (Handler handler : loggerHandlers) {
handler.publish(record);
}
//是否使用parent的Handler
final boolean useParentHdls = isSystemLogger
? logger.useParentHandlers
: logger.getUseParentHandlers();
if (!useParentHdls) {
break;
}
//判斷是否有parent是否有,如有則得到parent並循環獲取Handler
logger = isSystemLogger ? logger.parent : logger.getParent();
}
}
二、Handler
每一次log輸出都是由Handler完成
Logger logger = Logger.getLogger(LoggerDemo.class.getName());
logger.info("This is a test message!!!");
輸出:
May 05, 2020 9:49:08 PM com.LoggerDemo main
INFO: This is a test message!!!
這裏在創建getLogge
r的時候沒有綁定任何handler
,所以沒有自己的handler
,輸出時調用parent
綁定的handler
,RootLogger
默認會綁定一個ConsoleHandler
。而ConsoleHandler
的默認輸出是System.err
。
public ConsoleHandler() {
sealed = false;
configure();
setOutputStream(System.err);
sealed = true;
}
JDK自帶Handler
如下:
三、Formatter
Handler
中可以綁定一個Formatter
來處理輸出內容的格式。
依然ConsoleHandler
爲例,默認綁定的Formatter
是SimpleFormatter
。
private void configure() {
LogManager manager = LogManager.getLogManager();
String cname = getClass().getName();
setLevel(manager.getLevelProperty(cname +".level", Level.INFO));
setFilter(manager.getFilterProperty(cname +".filter", null));
//這裏設置默認的Formatter,由此可見可從配置中指定自己的formatter,通過properties文件配置的方式後續介紹。
setFormatter(manager.getFormatterProperty(cname +".formatter", new SimpleFormatter()));
try {
setEncoding(manager.getStringProperty(cname +".encoding", null));
} catch (Exception ex) {
try {
setEncoding(null);
} catch (Exception ex2) {
// doing a setEncoding with null should always work.
// assert false;
}
}
}
先看下SimpleFormatter的內容:
public synchronized String format(LogRecord record) {
dat.setTime(record.getMillis());
String source;
if (record.getSourceClassName() != null) {
source = record.getSourceClassName();
if (record.getSourceMethodName() != null) {
source += " " + record.getSourceMethodName();
}
} else {
source = record.getLoggerName();
}
String message = formatMessage(record);
String throwable = "";
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
record.getThrown().printStackTrace(pw);
pw.close();
throwable = sw.toString();
}
//這裏是核心的format,將所有要輸出的參數通過String.format的方式格式化。
return String.format(format,
dat,
source,
record.getLoggerName(),
record.getLevel().getLocalizedLevelName(),
message,
throwable);
}
看到這個我們就可以自己定義一個Formatter嘗試。
//得到Logger
Logger logger = Logger.getLogger(LoggerDemo.class.getName());
//禁用父級handler
logger.setUseParentHandlers(false);
//聲明自己的handler
Handler handler = new ConsoleHandler();
//設置Formatter
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
//指定格式具體內容請參考String的API,簡單的說 %+第n個參數,$+想要的格式。
return String.format("%1$tl:%1$tM:%1$tS %2$s %3$s %4$s: %n%5$s%n",
new Date(),
record.getLoggerName(),
record.getSourceMethodName(),
record.getLevel(),
record.getMessage()
//沒有詳細寫拋出異常的情況,參考源碼很容易理解
);
}
});
logger.addHandler(handler);
logger.info("This is a test message!!!");
輸出:
10:17:53 com.LoggerDemo main INFO:
This is a test message!!!
可以比較前面的輸出作爲對照,log的格式已經變了。
四、LogManager
前面我們已經通過代碼自定義了自己想要的Logger處理器。通過
LogManager
可以讀取.properties
的方式設置。
如下配置即可爲ConsoleHandler
添加Formatter
handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
用LogManager
裝載配置
LogManager.getLogManager().readConfiguration(ClassLoader.getSystemResourceAsStream("logging_default.properties"));
五、後記
還有一些Filter類也都大同小異的,不再贅述。