zoukankan      html  css  js  c++  java
  • 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,不在缀诉。

     

    前进时,请别遗忘了身后的脚印。
  • 相关阅读:
    EL表达式
    Java反射机制详解
    最小生成树-Prim算法和Kruskal算法
    最短路径—Dijkstra算法和Floyd算法
    servlet的转发与重定向
    SQL常用增删改查
    Java map遍历
    深入理解Java的接口和抽象类
    eclipse快捷键
    Vue父子组件数据双向绑定,子组件可修改props
  • 原文地址:https://www.cnblogs.com/liudaihuablogs/p/13462672.html
Copyright © 2011-2022 走看看