最近做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类也都大同小异的,不再赘述。