java.util.logging簡介

最近做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的時候可以通過調用父級Loggerhandler進行輸出log
Logger成員變量
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!!!

這裏在創建getLogger的時候沒有綁定任何handler,所以沒有自己的handler,輸出時調用parent綁定的handlerRootLogger默認會綁定一個ConsoleHandler。而ConsoleHandler的默認輸出是System.err

    public ConsoleHandler() {
        sealed = false;
        configure();
        setOutputStream(System.err);
        sealed = true;
    }

JDK自帶Handler如下:
Handler

三、Formatter

Handler中可以綁定一個Formatter來處理輸出內容的格式。
依然ConsoleHandler爲例,默認綁定的FormatterSimpleFormatter

	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類也都大同小異的,不再贅述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章