java自带的Logger日志系统
在练习Servlet基础的时候,我想着是要写一个输出请求链接的日志拦截器,但是真到写doFilter这块时,自己又一脸懵逼,凭借着感觉写,只知道有Logger这个对象,怎么使用的不会,百度一下简单抄了两行代码:
Logger logger = Logger.getLogger("filter");
logger.log(Level.ALL,拦截的地址:" + req.getRequestURL());
虽然也能正常工作,也有输出,但是自己整个过程一脸懵逼,很难受。下定决心打算研究研究这玩意。
自己英文不咋好,还好有中文版的JDK6可以阅读,找到java.util.logging下面的Logger类,阅读基本介绍。
Logger对象是用来记录特定系统或应用程序组件的日志消息。Logger具有层次结构,层次结构使用圆点分割表示,比如说FruitSales.AppleSales,就代表着如下图:
每一个Logger对象都有一个唯一的名称,这个名称可以是任意的字符串,但是最好是带有一定的层次结构,比如说基于包名或类名。
Logger fruitsaleslogger = Logger.getLogger("com.sales.fruitsales");
Logger applesaleslogger = Logger.getLogger("com.sales.fruitsales.applesales");
也可以创建"匿名"的Logger,但是使用匿名的Logger对象,它的名称不会存储在Logger名称空间中。
Logger名称空间就像是一个Map集合,存储着所有的logger对象,名称空间的key就是logger对象名,而value就是logger对象。上面说了,Logger具有一定的层次结构,每一个Logger对象都会跟踪一个"父"Logger,也就是Logger名称空间中与其最近的祖先,(TreeMap?)。拿这两个Logger对象来说:
Logger fruitsaleslogger = Logger.getLogger("com.sales.fruitsales");
Logger applesaleslogger = Logger.getLogger("com.sales.fruitsales.applesales")
我们创建的fruitsaleslogger对象,并没有指定它的祖先Logger,这里的祖先并不是说在它前面有圆点,圆点前面的就一定是它的祖先,不是这样的,我们并没有声明sales这个Logger对象所以fruitsaleslogger就不存在saleslogger,不存在它会继续向上找,找comlogger,同样也不存在comlogger。如果一个logger对象没有显示声明它的祖先logger,则其祖先logger为RootLogger,也就是根Logger对象,所以fruitsaleslogger的祖先Logger为RootLogger。而在上面applesaleslogger的祖先最近的是fruitsaleslogger,所有其祖先Logger即fruitsaleslogger。口说无凭,我们看一个例子:
package logger; import java.util.logging.Logger; public class LoggerTest { public static void main(String[] args) { Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales"); Logger appleSaleslogger = Logger.getLogger("com.sales.fruitsales.applesales"); Logger fspLogger = fruitSaleslogger.getParent(); Logger aspLogger = appleSaleslogger.getParent(); System.out.println("fruitSaleslogger:" + fruitSaleslogger); System.out.println("fruitSaleslogger的祖先Logger是:" + fspLogger); System.out.println("============================"); System.out.println("appleSaleslogger:" + appleSaleslogger); System.out.println("appleSaleslogger的祖先Logger是:" + aspLogger); } }
fruitSaleslogger:java.util.logging.Logger@3d4eac69 fruitSaleslogger的祖先Logger是:java.util.logging.LogManager$RootLogger@42a57993 ============================ appleSaleslogger:java.util.logging.Logger@75b84c92 appleSaleslogger的祖先Logger是:java.util.logging.Logger@3d4eac69
Logger通过调用getLogger(String name)工厂方法来获得Logger对象,在该方法的参数name若在Logger的命名空间里不存在,则创建一个新的logger对象,否则返回该logger对象。
Logger appLogger = Logger.getLogger("appLogger");
每个Logger对象都有一个与其相关的“Level”,标志着其输出的级别,如果“Level”被设置为null(默认为null),那么它的有效级别继承自父logger对象,这可以通过其父logger一直沿树向上递归得到(当心父logger会突然改变其级别,子类为null的也都会随着改变)。
null:默认为null时,级别低于INFO,但在CONFIG之上。
话1:” 对于每次日志记录调用,Logger最初都依照logger的有效日志级别对请求级别(例如SEVERE或FINE)进行简单的检查。如果请求级别低于日志级别,则日志记录调用将立即返回。“ 【摘抄API】
上面这两句话是什么意思?什么是有效日志级别和请求级别?如果你查阅过API就大概能明白,对于"每次日志记录的调用"说的即是:调用log(Level level, String mas)或info(String msg)等输出方法。"有效日志级别"即该logger对象最初设定的级别( logger.setLevel(Level level) ,也可能为null,为null时级别低于INFO,但在CONFIG之上)。"请求级别"即调用log方法里的Level参数或者info方法的Level.INFO。上面标红的那句话我们用实例证明,我们来测试一下:
例1:默认时我没设置Level,即Level = null(有效日志级别null)
public static void main(String[] args) { Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales"); fruitSaleslogger.severe("1==========");// 高于 null fruitSaleslogger.warning("2==========");// 高于 null fruitSaleslogger.info("3==========");//高于 null fruitSaleslogger.config("4==========");// 低于 null }
五月 05, 2019 6:48:05 下午 logger.LoggerTest main 严重: 1========== 五月 05, 2019 6:48:05 下午 logger.LoggerTest main 警告: 2========== 五月 05, 2019 6:48:05 下午 logger.LoggerTest main 信息: 3==========
例2:设置Level = Level.SEVERE(有效日志级别SEVERE)
public static void main(String[] args) { Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales"); fruitSaleslogger.setLevel(Level.SEVERE); fruitSaleslogger.severe("1=========="); // 等于 SEVERE fruitSaleslogger.warning("2=========="); // 低于 SEVERE fruitSaleslogger.info("3=========="); // 低于 SEVERE fruitSaleslogger.config("4=========="); // 低于 SEVERE }
五月 05, 2019 6:50:57 下午 logger.LoggerTest main
严重: 1==========
经过上面的测试我们知道,当调用一些输出方法时会进行日志级别的判断,对于低于有效日志级别的输出则直接忽略,那对于高于或等于有效日志级别的呢?再看API里的一句话,分析一下:
话2:”通过此初始(简单)测试后,Logger将分配一个LogRecord来描述日志记录消息。接着调用Filter(如果存在)进行更详细的检查,以确定是否应该发布该记录。如果检查通过,则将LogRecord发布到其输出Handler。在默认情况下,logger也将LogRecord沿树递推发布到其父Handler“【摘抄API】
LogRecord对象用于在日志框架和单个日志Handler之间传递日志请求。
Handler对象从Logger中获取日志信息,并将这些信息导出。例如,它可将这些信息写入控制台或文件中,也可以将这些信息发送到网络日志服务中,或将其转发到操作系统日志中。
摘自【知行流浪】
对于上面一张我自己根据理解画的图,参考源码,不管logger调用的是哪种输出方法最终都会调用doLog(LogRecord lr)最后转到log(LogRecord lr),如果说LogRecord是一个封装了logger的请求也能让人理解,在log(LogRecord lr)方法里,对LogRecord进行校验,校验其是否应该被发布,如果校验通过,则通知在这个logger对象上的所有观察者(Handler对象),Handler对象去调用publish(LogRecord lr)方法,发布这条日志。
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) { final Handler[] loggerHandlers = isSystemLogger ? logger.accessCheckedHandlers() : logger.getHandlers(); for (Handler handler : loggerHandlers) { handler.publish(record); } final boolean useParentHdls = isSystemLogger ? logger.useParentHandlers : logger.getUseParentHandlers(); if (!useParentHdls) { break; } logger = isSystemLogger ? logger.parent : logger.getParent(); } }
Handler对象才是最终用来发布日志的,这是一个抽象类,其有五个子类分别是:ConsoleHandler(发布日志到控制台),FileHandler(发布日志到文件),MemoryHandler(发布日志到内存),SocketHandler(发布日志到网络),StreamHandler(发布日志到流)
package logger; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; public class LoggerTest { public static void main(String[] args) throws Exception { Logger fruitSaleslogger = Logger.getLogger("com.sales.fruitsales"); // 设置有效日志级别 fruitSaleslogger.setLevel(Level.ALL); FileHandler ch = new FileHandler("C:\\Users\\admin\\Desktop\\ServletBase\\LoggerTest\\log\\logger.txt"); // 设置日志级别,指定该 Handler 所记录的信息级别。 ch.setLevel(Level.SEVERE); // 输出到文件的日志级别 fruitSaleslogger.addHandler(ch); fruitSaleslogger.severe("1=========="); fruitSaleslogger.warning("2=========="); fruitSaleslogger.info("3=========="); fruitSaleslogger.config("4=========="); } }
五月 05, 2019 8:56:17 下午 logger.LoggerTest main 严重: 1========== 五月 05, 2019 8:56:17 下午 logger.LoggerTest main 警告: 2========== 五月 05, 2019 8:56:18 下午 logger.LoggerTest main 信息: 3==========
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE log SYSTEM "logger.dtd"> <log> <record> <date>2019-05-05T20:56:17</date> <millis>1557060977954</millis> <sequence>0</sequence> <logger>com.sales.fruitsales</logger> <level>SEVERE</level> <class>logger.LoggerTest</class> <method>main</method> <thread>1</thread> <message>1==========</message> </record> </log>
如果你设置的Handler是ConsoleHandler则控制台可能会有部分输出重复,为了方便比对Level,我换成了FileHandler。可以看出控制台输出和Handler里的输出互不影响。