java自带的Logger日志系统

java自带的Logger日志系统

 

 

探索

一手文档

Logger是干什么的?

Logger对象的创建

结构

什么是Logger名称空间?

创建Logger对象的方式

Logger对象的级别

简述

分析API

LogRecord是干啥的?Handler是干啥的?

Handler对象

 

 

探索

在练习Servlet基础的时候,我想着是要写一个输出请求链接的日志拦截器,但是真到写doFilter这块时,自己又一脸懵逼,凭借着感觉写,只知道有Logger这个对象,怎么使用的不会,百度一下简单抄了两行代码:

Logger logger = Logger.getLogger("filter");
logger.log(Level.ALL,拦截的地址:" + req.getRequestURL());

虽然也能正常工作,也有输出,但是自己整个过程一脸懵逼,很难受。下定决心打算研究研究这玩意。

 

 

一手文档

自己英文不咋好,还好有中文版的JDK6可以阅读,找到java.util.logging下面的Logger类,阅读基本介绍。

 

Logger是干什么的?

Logger对象是用来记录特定系统或应用程序组件的日志消息。Logger具有层次结构,层次结构使用圆点分割表示,比如说FruitSales.AppleSales,就代表着如下图:

 

前提是你要有这个FruitSales才行。

 

Logger对象的创建

结构

每一个Logger对象都有一个唯一的名称,这个名称可以是任意的字符串,但是最好是带有一定的层次结构,比如说基于包名或类名。

Logger fruitsaleslogger = Logger.getLogger("com.sales.fruitsales");
Logger applesaleslogger = Logger.getLogger("com.sales.fruitsales.applesales");

也可以创建"匿名"的Logger,但是使用匿名的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对象的方式

Logger通过调用getLogger(String name)工厂方法来获得Logger对象,在该方法的参数name若在Logger的命名空间里不存在,则创建一个新的logger对象,否则返回该logger对象。

Logger appLogger = Logger.getLogger("appLogger");

Logger对象的级别

 

简述

每个Logger对象都有一个与其相关的“Level”,标志着其输出的级别,如果“Level”被设置为null(默认为null),那么它的有效级别继承自父logger对象,这可以通过其父logger一直沿树向上递归得到(当心父logger会突然改变其级别,子类为null的也都会随着改变)。

各级别按降序排列如下:

 SEVERE(最高值) :指示严重失败的消息级别。

 WARNING:指示潜在问题的消息级别。

 INFO(最常用):报告消息的消息级别。

 CONFIG:用于静态配置消息的消息级别。

 FINE:提供跟踪消息的消息级别。

 FINER:指示一条相当详细的跟踪消息。

 FINEST(最低值):指示一条最详细的跟踪消息。

 

OFF:是一个可用于关闭日志记录的特殊级别。

ALL:指示应该记录所有消息。

null:默认为null时,级别低于INFO,但在CONFIG之上。

 

 

分析API

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是干啥的?

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对象

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==========

logger.txt文件

<?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里的输出互不影响。

Handler中可以设置一些Fileter,不在缀诉。

 

前进时,请别遗忘了身后的脚印。
原文地址:https://www.cnblogs.com/liudaihuablogs/p/13462672.html