精华内容
下载资源
问答
  • jdk自带的日志
    2021-03-11 12:42:48

    现在项目中,大多用log4j等第三方日志框架,用这些框架确实有原因,而且确实配置简单,好用。因为一个传统项目不想用第三方日志框架,想用jdk自带的日志来记录日志,所以总结了下经验,希望对大家有所帮助。本文讲解的是不用自写工具类,简单几个步骤就完成日志的记录。

    步骤:1.创建  Logger logger;在网上查阅了相关资料,这个步骤对下面的步骤很重要。

    2.创建  FileHandler fileHandler 定义日志文件保存的路径和日志文件生成的规则,很简单的。

    3.FileHandler fileHandler=new FileHandler("E:\\Log\\JDKLog1"+"\\"+sdf.format(new Date())+".log",true);官方标配工具。

    logger.addHandler(fileHandler);//日志输出文件      这个步骤必须要有,不然无法生成日志文件。

    4.定义日志级别输出信息就可以了,logger.log(Level.INFO, e.toString());

    5.fileHandler.setFormatter(new SimpleFormatter());//输出格式   第五步,只是为了定义输出日志的格式,实现前四步就可以生成日志文件,亲测有效的!

    很简单的步骤,只是为了给大家提供另一种日志生成方式,不喜勿喷,都是相互学习。

    源码如下 :

    package pac.com.pac;

    import java.io.File;

    import java.io.IOException;

    import java.text.SimpleDateFormat;

    import java.util.Date;

    import java.util.logging.FileHandler;

    import java.util.logging.Level;

    import java.util.logging.Logger;

    import java.util.logging.SimpleFormatter;

    import javax.sound.midi.MidiDevice.Info;

    import org.junit.Test;

    /**

    *

    *

    Title: TestLoger2 

    *

    Description: 

    * @author Administrator

    * @date 2019年5月18日

    */

    public class TestLoger2 {

    private static Logger logger=Logger.getLogger(TestLoger2.class.getName());

    @Test

    public void test05() throws Exception {

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    try {

    int i=1/0;

    } catch (Exception e) {

    FileHandler fileHandler=new FileHandler("E:\\Log\\JDKLog1"+"\\"+sdf.format(new Date())+".log",true);

    logger.addHandler(fileHandler);//日志输出文件

    //logger.addHandler(new ConsoleHandler());//输出到控制台

    //logger.setLevel(Level.ALL);

    fileHandler.setFormatter(new SimpleFormatter());//输出格式

    logger.log(Level.INFO, e.toString());

    }

    }

    }

    更多相关内容
  • JDK自带Logging的使用

    大家调试代码的时候是不是常常用System.out.println()打印出执行过程中的某些变量,观察每一步的结果与代码逻辑是否符合,然后有针对性地修改代码。然后代码改好之后又要删除没有用的System.out.println()语句了,万一代码还有错,又开始重复上面的操作。

    java为这种问题的发生提供的解决方法是使用日志(Logging),它的目的是为了取代System.out.println()

    输出日志,而不是用System.out.println(),有以下几个好处

    1. 可以设置输出样式,避免自己每次都写"ERROR: " + var
    2. 可以设置输出级别,禁止某些级别输出。例如,只输出错误日志;
    3. 可以被重定向到文件,这样可以在程序运行结束后查看日志;
    4. 可以按包名控制日志级别,只输出某些包打的日志;
    5. 可以……

    那如何使用日志?

    因为Java标准库内置了日志包java.util.logging,我们可以直接用。先看一个简单的例子:

    // logging
    import java.util.logging.Level;
    import java.util.logging.Logger;
    public class Hello {
        public static void main(String[] args) {
            Logger logger = Logger.getGlobal();
            logger.info("start process...");
            logger.warning("memory is running out...");
            logger.fine("ignored.");
            logger.severe("process will be terminated...");
        }
    }
    
    /**输出**/
    Jan 25, 2021 3:28:16 AM Hello main
    INFO: start process...
    Jan 25, 2021 3:28:16 AM Hello main
    WARNING: memory is running out...
    Jan 25, 2021 3:28:16 AM Hello main
    SEVERE: process will be terminated...

    对比可见,使用日志最大的好处是,它自动打印了时间、调用类、调用方法等很多有用的信息。

    再仔细观察发现,4条日志,只打印了3条,logger.fine()没有打印。这是因为,日志的输出可以设定级别。JDK的Logging定义了7个日志级别,从严重到普通:

    • SEVERE
    • WARNING
    • INFO
    • CONFIG
    • FINE
    • FINER
    • FINEST

    因为默认级别是INFO,因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。

    但是

    使用Java标准库内置的Logging有以下局限:

    Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()方法,就无法修改配置;

    配置不太方便,需要在JVM启动时传递参数-Djava.util.logging.config.file=<config-file-name>

    因此,Java标准库内置的Logging使用并不是非常广泛。

    小结

    日志是为了替代System.out.println(),可以定义格式,重定向到文件等;

    日志可以存档,便于追踪问题;

    日志记录可以按级别分类,便于打开或关闭某些级别;

    可以根据配置文件调整日志,无需修改代码;

    Java标准库提供了java.util.logging来实现日志功能。

     

     

    展开全文
  • 文章目录前言一、JUL架构介绍1.1、认识不同组件1.2、Logger1.3、Handler二、输出日志信息三、自定义日志级别配置3.1、认识Level类3.2、输出不同等级日志3.2、自定义日志级别(console与文件输出)四、Logger的子父类...

    前言

    在平时跟着教程做项目时,对于日志处理往往都是直接引入jar包,添加一个配置文件,对于日志的概念是十分模糊的,在寒假这段时间阅读书籍《Java核心技术》中的日志章节时,感觉并不是特别能够理解,接着就去b站搜了搜相关学习视频,搜到了黑马教程:黑马程序员java日志框架教程,全面深入学习多种java日志框架,花了半个月时间跟着视频学习并结合阅读其他博主的文章输出了多篇博客,也算是入了个门。

    JULJDK自带的一个日志,对于日志的学习可以先从JUL入手,会有利于之后学习其他的日志框架。

    所有博客文件目录索引(包含日志框架系列学习):博客目录索引(持续更新)



    一、JUL架构介绍

    1.1、认识不同组件

    image-20210225225731904

    • LoggerHandler都可设置过滤器Filter

    日志靠几个组件完成

    • Loggers(日志记录器):Logger通常是应用程序访问日志系统的入口程序,负责捕捉事件并将其发送给何时的Appender,其关联着一组Handler
    • Appenders(输出源):也称为Handlers,负责从Logger中取出日志消息,并使用Layouts(即Formatters)来格式化信息,然后将消息发送出去。一个logger可以有多个Handlers。可通过抽象类Handler下的具体实现类来决定输出位置,可输出多个位置。(控制台、文件或其他日志收集系统)
    • Layouts(布局器):称为Formatters,负责对日志事件中的数据进行转换和格式化,通过使用抽象类Formatter下的指定实例来决定输出格式。
    • Filters:过滤器,根据需要来定制哪些日志会被记录或放过。

    日志输出大致流程

    1. 首先获取到Logger实例(默认日志等级为INFO),该实例初始化会自动配置一个rootlogger(即它的父logger),这个rootlogger中会有一个Consolehandler(用于将输出到屏幕)。
    2. 当使用Logger实例来调用方法输出日志时,首先会比对Logger本身的日志等级以及其过滤器。
    3. 接着会依次执行Logger实例中的handlers集合中的handlerpublish()方法来输出日志信息,再此之前会与handler的日志等级作比较以及handlerfilter过滤器。(若无handlers则跳过)
    4. 接着会根据Logger中的useParentHandlers布尔值来判断是否执行rootlogger中的handlers,操作如同3一样。

    注意LoggerHandler都有自己对应的日志level等级,初始默认都为INFO(800)。上面的流程初始阶段了解即可,对于详细的执行流程可见最后总结。



    1.2、Logger

    image-20210226230624352

    Logger:是一个独立的类,它有一个子类为RootLogger(是LogManager类的内部类)

    包含方法

    • static Logger getLogger(String name):获取一个Logger实例,其有参构造器为私有的。
    • void log(Level level, String msg):用于打印日志信息,第一个参数为日志等级,第二个参数为日志信息。
    • void info(String msg):打印Level.INFO级别的日志,实际上也是调用的log()方法。对应类似方法还有void warning(String msg)以及其他几个级别日志方法,更加方便打印日志。
    • void setUseParentHandlers(boolean useParentHandlers):不使用默认自带的父类的handler
    • void setLevel(Level newLevel):设置日志等级。(默认为INFO)
    • void addHandler(Handler handler):添加指定类型handlerhandlers集合中。
    • void setFilter(Filter newFilter):设置过滤器。


    1.3、Handler

    Handler:是一个抽象类,其中包含了set/get方法。可设置对应的FormatterFilter,并自身带有对应的日志等级。

    image-20210225232243613

    相关实现类:用于输出到不同位置

    • StreamHandler:将日志通过OutputStream输出。
      • FileHandler:对应的OutputStream对象是FileOutputStream,能够将日志输出到文件。
      • ConsoleHandler:对应的OutputStream对象是System.err,会将日志输出到控制台。
      • SocketHandler:对应的OutputStream对象是Socket.getOutputStream(),会将日志输出到网络套接字。
    • MemoryHandler:将日志输出到一个内存缓冲区。

    相关方法如下

    image-20210225232611663

    • synchronized void setLevel(Level newLevel):设置Handler的日志等级。
    • synchronized void setFilter(Filter newFilter):设置对应过滤器。
    • synchronized void setFormatter(Formatter newFormatter):设置对应的格式化输出器。
    • synchronized void setEncoding(String encoding):设置输出时的字符编码。
    • ErrorManager getErrorManager():返回错误处理器,errorManager用来处理日志记录过程中发生的异常信息。
    • synchronized void publish(LogRecord record):用于输出日志。


    二、输出日志信息

    JUL:JDK自带的日志实现依赖。

    输出Info级别日志的三种方式:

    @Test
    public void test01(){
        //方式一:通过Logger.getLogger()方法获取日志记录对象
        //有参构造是全限定名
        Logger logger = Logger.getLogger("xyz.changlu.LogTest");
        logger.info("hello jul");
    
        //方式二:通用方法进行日志输出
        logger.log(Level.INFO,"自定义level为INFO,报错");
    
        //方式三:通过占位符方式
        String methodName = "test01";
        int msg = 2;
        logger.log(Level.INFO,"方法名{0},次数为{1}",new Object[]{methodName,msg});
    }
    
    • 方式一的logger.info()内部实际上也是调用的log()方法(传INFO参数即可)。
    • log()方法第一个参数可以指定任意级别,后面msg则是输出到控制台信息的内容;重载方法也可以通过占位符方式填入指定要输出的信息。

    image-20210225210205358

    • 第一行信息自带日期时间,包名及方法都是自动获取输出的;第二行的信息:后则是我们输入的msg


    三、自定义日志级别配置

    3.1、认识Level类

    Level:是一个枚举类(jdk1.5以前自定义枚举类)。

    该类中包含七个不同等级的枚举实例,等级从高到低如下:

    • SEVERE:造成了程序的终止,可以使用该等级来记录。(value:1000)
    • WARNING:记录程序发生的一些问题,但问题不会造成程序终止。(value:900)
    • INFO:消息记录,记录数据库的连接信息,IO的传递信息等等。(value:800)
    • CONFIG:配置信息,加载了配置文件,读取配置的一些参数。(value:700)
    • 下面的三个都是用来Debug日志记录的消息,记录程序运行的状态跟执行的流程,参数的传递信息。这三者value级别的大小不同,FINE低一些,FINEST高一些,这三个开发中拿一个即可
      • FINE:(value:500)
      • FINER:(value:400)
      • FINEST:(value:300)
    • ALL:所有日志级别都能执行。(value:Integer.MIN_VALUE)
    • OFF:所有日志都不能执行。(value:Integer.MAX_VALUE)

    源码实例

    image-20210225224753016

    • value是枚举实例在构造时填入的参数,决定了该Level实例的等级。


    3.2、输出不同等级日志

    上面枚举实例对应的value起到什么作用呢?

    • 用于当前logger实例的日志等级value值与调用log()方法输出指定日志级别的value值对比,有限制日志输出的作用。

    我们看下面的例子:同时输出7个不同等级的日志

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu.test01");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    

    image-20210225213103607

    • 可以看到只有severewarninginfo等级能够输出msg信息。

    那么为什么只输出了前三个等级的日志呢?我们看Logger源码:

    //这里调用的是Logger类的log()
    public void log(Level level, String msg) {
        if (!isLoggable(level)) {//首先会判断是否大于当前logger的等级,一旦小于返回isLoggable()返回false,则会执行return结束方法
            return;
        }
        //传入进来的level符合现有等级,那么就会执行下去通过handler进行输出信息
        LogRecord lr = new LogRecord(level, msg);
        doLog(lr);
    }
    
    public boolean isLoggable(Level level) {
        //这里levelValue是现有logger实例中的日志等级默认为INFO(800),一旦小于800或等于Integer.MAX_VALUE(Level.OFF实例值)返回false
        if (level.intValue() < levelValue || levelValue == offValue) {
            return false;
        }
        return true;
    }
    

    能够看到在调用log()方法会去与自己本身的日志等级比较(初始默认为INFO),所以只会输出INFO等级及以上。

    我们可通过使用void setLevel(Level newLevel)来设置当前Logger的日志等级:

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu.test01");
        logger.setLevel(Level.WARNING);
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    

    image-20210225231512933

    • 果然生效了,当设置Logger的当前日志等级为WARNING时,有对应效果。

    那我们接着设置低级别的呢,如logger.setLevel(Level.CONFIG);,结果却并没有输出config的日志信息。其实原因是因为在进行日志输出时,并不仅仅会比对Logger本身的日志等级还会比其Handler的日志等级(初始默认也为INFO)。

    若是我们想要输出所有日志等级的日志信息呢

    • logger实例以及其handler的日志等级都设置为ALL即可。
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu.test01");
        //不执行rootlogger中的handlers
        logger.setUseParentHandlers(false);
    
        //自定义handler
        ConsoleHandler ch = new ConsoleHandler();
        ch.setFormatter(new SimpleFormatter());
        //将实例logger与对应handler都设置自定义的日志等级
        ch.setLevel(Level.ALL);
        logger.setLevel(Level.ALL);
        //将handler添加到logger的handlers集合中
        logger.addHandler(ch);
    
        //输出不同等级的日志
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    
    • 获得的logger实例中会有一个rootlogger,我们不自定义handler时,使用log()输出到控制台信息就是通过rootlogger中的ConsoleHandler输出的。
    • 第4行:我们使用该方法,传入false表示不执行rootloggerhandlers
    • 第7行-13行:自定义handler并设置对应的转换器,接着将handler添加到logger中。其中有两个关键操作是设置自定义handler的日志等级以及设置logger实例的日志等级为Level.ALL。ALL对应的值为Integer.MIN_VALUE,所以所有级别的日志都能够输出显示。


    3.2、自定义日志级别(console与文件输出)

    @Test
    public void test01() throws IOException {
        //输出日志到调试窗口
        Logger logger = Logger.getLogger("xyz.changlu.test01");
        //不将记录发送到父类处理器中
        logger.setUseParentHandlers(false);
        
        //1、定义窗口处理器ConsoleHandler
        ConsoleHandler ch = new ConsoleHandler();
        ch.setFormatter(new SimpleFormatter());//ConsoleHandler设置简单格式化输出器
        //2、定义文件处理器
        FileHandler fh = new FileHandler("jul.log");//指定路径(这里就是工程目录下)
        fh.setFormatter(new SimpleFormatter());
        //logger器中添加两个handler
        logger.addHandler(ch);
        logger.addHandler(fh);
    
        //输出的所有等级的日志信息到对应handler指定的区域(窗口以及文件)
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    
    • 第4、6行:通过Logger静态方法getLogger()会获取到一个Logger实例,它有个rootlogger,并自带handlers(ConsoleHandler,其使用的SimpleFormatter转换器),由于不使用rootlogger中的handlers,所以我们调用setUseParentHandlers(false)方法传入参数false表示不执行rootloggerhandlers(防止重复打印)。
    • 第9、12行:就是设置不同的handler(输出到窗口、文件),并将对应的转换器放置到hander中。
    • 第15、16行:将多个handler依次放置到logger实例中,一旦执行log()方法就会执行其所有的handler

    image-20210225235441297

    image-20210225235457002



    四、Logger的子父类关系

    4.1、认识根Logger

    探究初始创建Logger与根Logger的关系

    JUL中的Logger之间存在子父类关系,当我们初始化一个Logger时,会默认创建一个rootlogger(一般情况下,除非下面①)

    ①直接创建一个rootlogger(name=""就是):

    ①我们先创建一个顶层Logger(即name=""):

        @Test
        public void test01(){
            //传入空字符串获取Logger实例
            Logger logger2 = Logger.getLogger("");
            System.out.println(logger2);
            System.out.println(logger2.getParent());//打印其父类Logger
        }
    

    image-20210226233921260

    • 当传入的name为""时,就会默认获取一个RootLogger实例,即为根Logger,因为是最顶层的了,所以就没有父Logger了。

    ②我们接着传入包名来获取Logger实例

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu");
        System.out.println(logger+",name="+logger.getName());
        System.out.println(logger.getParent()+",name="+logger.getParent().getName());
    }
    

    image-20210226234526741

    • 可以看到当我们创建一个name为"xyz.changlu"Logger实例时,其父类则是根Logger,其name=""

    ③创建有层级关系的Logger实例:

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu");
        //传入name为xyz.changlu的上级包名为xyz的Logger实例
        Logger logger2 = Logger.getLogger("xyz");
        System.out.println("logger="+logger+",name="+logger.getName());
        System.out.println("logger2="+logger.getParent()+",name="+logger.getParent().getName());
        System.out.println(logger.getParent() == logger2);
        System.out.println("logger2的根looger="+logger2.getParent()+",name="+logger2.getParent().getName());
    }
    

    image-20210226235105724

    • 注意当我们创建了logger的上级Logger实例(name为xyz)时,该实例的上级Logger默认会变为logger2的实例了,此时Logger2的父类则为根Logger了。(可以注意到当定义了有层级相关的Logger时会自动继承相关关系,也与包的层级有关)

    总结

    1. 当我们初始化一个Logger实例时(name!=""情况下),会默认自带一个rootlogger实例,可通过getParent()获取到,其name=""
    2. 当我们初始化一个Logger实例时,设置其name=""会直接获得一个rootlogger实例,此时就没有父Logger了。
    3. 当我们定义多个Logger实例时他们的name具有层级关系,例如:xyz.changluxyz,那么他们会自动建立关联,name=xyz.changluLogger实例其父Logger则为刚刚定义的name=xyzLogger实例,name=xyz的父Logger则为根Logger

    查看rootlogger中的handler、formatter、level等级

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu");
        Logger parent = logger.getParent();//为根Logger实例
        System.out.println("rootLogger:"+parent);
        System.out.println("rootlogger的name:"+parent.getName());
        System.out.println("rootlogger的filter:"+parent.getFilter());
        System.out.println("rootlogger的level:"+parent.getLevel());
        System.out.println("handler:"+parent.getHandlers()[0]);
        System.out.println("handler的Formatter:"+parent.getHandlers()[0].getFormatter());
        System.out.println("handler的level:"+parent.getHandlers()[0].getLevel());
        System.out.println("handler的filter:"+parent.getHandlers()[0].getFilter());
    }
    

    image-20210227001038348 +

    • 获得rootlogger还有一种方式:Logger.getLogger("") 空字符串即可。
    • 可以看到rootloggerlevel与该loggerhandlerlevel都为INFOHandlerConsoleHandlerFormatterSimpleFormatter

    此时对于logger算是有一些了解了,不过还不够继续往下看。



    4.2、Logger的info(msg)执行流程*

    这里默认name=xyz.changlulogger实例,其父Loggerrootlogger

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu");
        logger.info("报错啦");
    }
    

    过程如下

    1. 比较该logger实例的level是否高于INFO。(这里由于logger本身初始化并没有设定级别,所以使用的是rootlogger的日志级别)。
    2. 发现>=INFO,则继续,接着调用loggerFilter实例的isLoggable(),若没有添加,就不进行过滤。
    3. 接着开始依次调用loggerhandlers(即handler集合),由于初始化没有指定handler,就不进行日志输出。
    4. 判断useParentHandlersboolean参数是否为true,默认为true,接着调用rootloggerhandler实例的publish()方法。
    5. rootloggerhandlerpublish()方法中判断该handler的级别是否>=INFO,由于默认为INFO,大于继续执行。
    6. 接着调用rootloggerhandlerisLoggable()进行过滤,没有则不过滤。
    7. 使用rootloggerConsoleHandlergetFormatter().format(LogRecord)进行格式化(默认为SimpleFormatter)。
    8. 最终调用write()将格式化后数据输出到控制台。

    注意注意:在抛到上层的logger时并不会判断该loggerlevel,仅仅是handlerslevel

    针对于Loggerhandler规范:

    一个Logger对应1个Level,对应0个或1个Filter,对应0个或多个Handler,对应0个或1个ParentLogger 。
    一个Handler对应1个Level,对应0个或1个Filter,对应1个Formatter。
    

    通过上面说明,我们来看下面例子,请说出打印的内容有些什么:

    public class LogTest {
        private static final Logger logger1 = Logger.getLogger("");
        private static final Logger logger2 = Logger.getLogger("cn");
        private static final Logger logger3 = Logger.getLogger("cn.codecrazy");
        static {
            logger2.addHandler(new ConsoleHandler());
            logger3.addHandler(new ConsoleHandler());
            logger2.setLevel(Level.WARNING);
            logger3.setLevel(Level.INFO);
        }
    
        public static void main(String[] args) {
            System.out.println(logger2.getLevel());
            System.out.println(logger2.getHandlers()[0].getLevel());
            logger1.info("logger1");
            logger2.info("logger2");
            logger3.info("logger3");
        }
    }
    

    image-20210228234003684

    • 若是创建一个ConsoleHandler,其会自动初始化一个INFO的日志等级(剧透一下是因为读取了默认的配置文件设置的等级)。

    介绍整个流程:首先定义了三个日志实例,互相包含层级关系,在static代码块中将logger2中的日志等级设置为WARNING,并且添加了一个ConsoleHandler(该handler会自动赋予自己的一个INFO日志等级);接着给logger3中的日志等级设置为INFO,并也添加了一个ConsoleHandler

    开始进入main()方法,之前设置了logger2的等级,第一行则输出WARNING,logger2中的handler就是刚刚的consolehandler,默认等级为INFO。接着开始INFO打印输出,

    ①首先是第15行,对logger1进行日志输出(其等级为INFO),由于该logger实例是rootloggerhandler默认日志等级都为INFO,所以直接打印。

    ②接着是16行,首先会比对logger2中的日志等级(由于代码块中设置为WARNING),所以直接方法过程中直接return了,没有打印。

    ③接着是17行,首先会比对logger3的日志等级,本身为INFO,所以通过,接着会遍历logger3handlers,其中consolehandler日志等级默认为INFO通过,所以在loggers中打印了第一次;接着会调用执行父logger(即为logger2)中的handlers,比对其handler的日志等级为INFO,所以再次通过打印第二次;最后再次调用父logger(这次为rootlogger)的handlers,由于是consolehandler,所以日志等级还是INFO,所以再次打印。

    所以logger1打印了1次,logger3打印了3次。

    本部分参考案例来源: Java Logging之JUL系列——Logger Hierarchy



    4.3、设置日志等级

    logger实例本身设置高的日志等级,logger尝试打印

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu");
        Logger parent = logger.getParent();//rootlogger
        //logger实例本身设置日志等级
        logger.setLevel(Level.WARNING);
        System.out.println(parent.getLevel());
        System.out.println(logger.getLevel());
        logger.info("cl报错啦");
    }
    

    image-20210227004605993

    • 该logger在进行日志等级判定时就会判定<INFO,所以无打印。

    rootlogger设置WARNING等级,logger来打印日志

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("xyz.changlu");
        Logger parent = logger.getParent();//rootlogger
        //logger实例本身设置日志等级
        parent.setLevel(Level.WARNING);
        logger.info("cl报错啦");
    }
    

    image-20210227004645036

    rootlogger设置等级时则会影响到其子loggerlevel等级。



    五、日志的配置文件

    5.1、初探源码(读取配置文件部分,前)*

    当我们使用Logger.getLogger("")来获取一个Logger实例时,会默认在根Logger中添加一个Conslehandler(带有SimpleFormatter转换器)。

    • 对于rootlogger默认添加的handler以及formatter实际上是通过一个配置文件来进行配置的,其配置文件是JDK自带的,名称为logging.properteis

    接下来我们来看源码中的内容,根据数字的顺序:只针对于读取配置文件

    private static final Logger logger1 = Logger.getLogger("");//调用该方法开始读源码
    
    public class Logger {
        
        //1、getLogger()获取实例的方法
    	@CallerSensitive
        public static Logger getLogger(String name) {return demandLogger(name, null, Reflection.getCallerClass());//去到2
        }
        
        //2、demandLogger():日志记录器
        private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
            LogManager manager = LogManager.getLogManager();//去到3
            ....
            //该方法会创建logger实例的操作,包含其中的handlers(根据配置文件是否有反射创建实例)以及level等...,其level若没有会去拿到父类的level
            return manager.demandLogger(name, resourceBundleName, caller);//返回logger实例对象
        }
    }
    
    public class LogManager {
        
        private static final LogManager manager;//该类是一个单例实例
        
        //3、获取到LogManager的单例属性
        public static LogManager getLogManager() {
            if (manager != null) {
                manager.ensureLogManagerInitialized();//去往4
            }
            return manager;
        }
        
        //4、进行LogManager读取配置文件与初始化操作
        final void ensureLogManagerInitialized() {
            final LogManager owner = this;
            if (initializationDone || owner != manager) {
                return;
            }
            synchronized(this) {
                final boolean isRecursiveInitialization = (initializedCalled == true);
                assert initializedCalled || !initializationDone
                if (isRecursiveInitialization || initializationDone) {
                    return;
                }
                initializedCalled = true;
                try {
                    AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        @Override
                        public Object run() {
                            assert rootLogger == null;
                            assert initializedCalled && !initializationDone;
                            //注意这里
                            owner.readPrimordialConfiguration();//去往5
    						
                            //own就是logManager实例,对rootLogger进行初始化
                            owner.rootLogger = owner.new RootLogger();
                            //add方法中会实例化LoggerContext,其中包含了一个hashtable实例,键为logger名称,LoggerWeakRef引用(属性有name、LogNode、parentRef,分别为名称、节点以及父节点引用,logNode存储一个完整的logger信息)
                            owner.addLogger(owner.rootLogger);
                            if (!owner.rootLogger.isLevelInitialized()) {
                                owner.rootLogger.setLevel(defaultLevel);
                            }
                            //这里是获取一个logger实例(为了向下兼容1.7的)
                            final Logger global = Logger.global;
                            owner.addLogger(global);
                            return null;
                        }
                    });
                } finally {
                    initializationDone = true;
                }
            }
        }
        
        //5、读取原始配置文件
        private void readPrimordialConfiguration() {
            if (!readPrimordialConfiguration) {
                synchronized (this) {
                    if (!readPrimordialConfiguration) {
                        if (System.out == null) {
                            return;
                        }
                        readPrimordialConfiguration = true;
    
                        try {
                            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                                    @Override
                                    public Void run() throws Exception {
                                        //继续调用读取配置文件方法(很重要的地方)
                                        readConfiguration();//去往6
    
                                        // Platform loggers begin to delegate to java.util.logging.Logger
                                        sun.util.logging.PlatformLogger.redirectPlatformLoggers();
                                        return null;
                                    }
                                });
                        } catch (Exception ex) {
                            assert false : "Exception raised while reading logging configuration: " + ex;
                        }
                    }
                }
            }
        }
        
        //6、读取配置文件(本部分最关键地方)
        public void readConfiguration() throws IOException, SecurityException {
            checkPermission();
    
            //自定义配置类,负责完成配置信息的初始化。
            //这里cname就是对应的配置类名称,方便下面通过类加载来加载对应类进行配置信息。
            String cname = System.getProperty("java.util.logging.config.class");
            if (cname != null) {
                try {
                    try {
                        Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
                        clz.newInstance();
                        return;
                    } catch (ClassNotFoundException ex) {
                        Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
                        clz.newInstance();
                        return;
                    }
                } catch (Exception ex) {
                    System.err.println("Logging configuration class \"" + cname + "\" failed");
                    System.err.println("" + ex);
                    // keep going and useful config file.
                }
            }
            
            //自定义配置文件
    		//获取System里Properties实例中的键
            String fname = System.getProperty("java.util.logging.config.file");
            //若是没有获取到,fname为null,则会读取jdk目录中官方的logging.properties
            if (fname == null) {
                //获取jdk中的配置文件路径
                //获取系统参数,Java 安装目录,我的是C:\Program Files\Java\jdk1.8.0_201\jre
                fname = System.getProperty("java.home");
                //若没有对应参数,则会报错
                if (fname == null) {
                    throw new Error("Can't find java.home ??");
                }
                //此时f路径为:C:\Program Files\Java\jdk1.8.0_201\jre\lib
                File f = new File(fname, "lib");
                //在添加一个子目录:C:\Program Files\Java\jdk1.8.0_201\jre\lib\logging.properties
                f = new File(f, "logging.properties");
                fname = f.getCanonicalPath();
            }
            //使用文件输入流来读取该配置文件:logging.properties
            try (final InputStream in = new FileInputStream(fname)) {
                //添加缓冲流,加快读取速度
                final BufferedInputStream bin = new BufferedInputStream(in);
                //读取之前读取的配置文件输入流
                readConfiguration(bin);
            }
        }
        
        //7、读取文件输入流操作
        public void readConfiguration(InputStream ins) throws IOException, SecurityException {
            checkPermission();
            reset();
    		
            //该props是一个properties实例,这里将参数中的输入流放置到其中,为之后键值对读取做准备。
            props.load(ins);
            ...
        }
    }
    
    • 上面的源码分析暂时只到配置文件为止,不再往下深入更细节的内容,我们只要知道,在不进行自定义配置文件时,会自动读取jre/lib目录中的logging.properties到一个输入流中,再调用LoggerManager实例(单例)的readConfiguration(bin);,bin则为对应的配置文件输入流。
    • 第122行:fname指的就是对应配置文件的路径,这里为:C:\Program Files\Java\jdk1.8.0_201\jre\lib\logging.properties。

    若是我们不进行自定义配置类或自定义配置文件,那么就会自动读取jre/lib目录下官方提供的配置文件!

    源码看完了,我们就去看一下这个logging.properties里面的内容吧:

    image-20210301171911899

    • 确实有logging.properties配置文件。

    清理了注释后内容如下

    # RootLogger 顶级父元素指定的默认处理器 ConsoleHandler,可添加多个用,隔开
    handlers= java.util.logging.ConsoleHandler
    
    # RootLogger 顶级父元素默认的日志级别为INFO
    .level= INFO
    
    # 若是上面handlers中多加了一个FileHandler就生效8-11行配置
    java.util.logging.FileHandler.pattern = %h/java%u.log  # ①%h表示当前用户目录路径,我的为C:\Users\93997;②%u对  # 应下面的count,范围为[0,count)
    java.util.logging.FileHandler.limit = 50000  # 输出日志文件限制大小(50000字节)
    java.util.logging.FileHandler.count = 1      # 定义上面%h的范围
    java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter  # 设置FileHandler中的转换器为    # XMLFormatter (XML格式)
    
    # 对应上面handlers中的ConsoleHandler进行配置
    java.util.logging.ConsoleHandler.level = INFO  # handler的日志等级为INFO
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # 该handler转换器为        # SimpleForamtter
    
    # 特殊logger日志的默认等级,当设置name为com.xyz.foo的logger实例,其日志等级为SEVERE
    com.xyz.foo.level = SEVERE  # 
    

    所以我们刚开始获取一个logger实例会自带一个handlerformatter,与这个配置文件关系很大。



    5.2、自定义配置文件

    首先要定义一个自定义配置文件,接着通过调用方法或在java命令中添加参数(目的就是让LogManager的单例加载到该配置文件),对于配置文件我们可以借鉴jre/lib下的logging.properties,在其基础上进行自定义配置。

    名字可以随意如:xxx.properteis,后缀应当使用properteis,因为源码中读取配置文件的输入流使用的Propertiesload()方法。

    我们来自定义一个吧,还是logging.properties,放置的位置若是Maven项目的话放置在resource目录下;若是普通java工程放置在src目录下即可:

    # ①额外增加了一个FileHandler,用于输出到文件中
    handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
    # ②默认日志等级为ALL(那么)
    .level= ALL
    
    # ④在.log文件上层多添加了一个logs目录
    java.util.logging.FileHandler.pattern = %h/logs/java%u.log
    java.util.logging.FileHandler.limit = 50000
    java.util.logging.FileHandler.count = 1
    # ⑤转换器更改为简单转换
    java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
    
    # ③设置handler的日志等级为ALL
    java.util.logging.ConsoleHandler.level = ALL
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
    
    com.xyz.foo.level = SEVERE
    
    • 增添内容如上数字标记部分。

    因为创建的是Maven工程,所以放在resources目录中:

    image-20210301183746077


    方式如下

    方式一:使用System.setProperties()来配置文件

    System.setProperty("java.util.logging.config.file",
            "C:\\Users\\93997\\Desktop\\工程文件\\logdemo\\src\\main\\resources\\logging.properties");
    

    解析:我们之前看源码时,针对于自定义配置文件是通过System.getProperty()来获取对应的文件路径的,所以我们在main()方法前添加对应的配置文件路径之后调用Logger.getLogger()方法就会加载我们的自定义配置文件。


    方式二:在执行字节码时添加命令参数

    image-20210301190810718

    • -D java.util.logging.config.file="C:\Users\93997\Desktop\projectexers\logdemo\src\main\resources\logging.properties",值应当为详细路径。

    添加该命令之后,运行程序则会自动添加到系统参数中。


    方式三:使用LogManager的单例实例调用readConfiguration()方法读取配置文件输入流

    @Test
    public void test01() throws IOException {
        //1、通过系统类加载器的getResourceAsStream()来加载配置文件,返回输入流
        InputStream is = LogTest.class.getClassLoader().getResourceAsStream("logging.properties");
        //2、使用LogManager的单例来读取该配置文件(与源码读取官方logging.properties方法类似)
        LogManager.getLogManager().readConfiguration(is);
    
        //测试
        Logger logger = Logger.getLogger("xyz.changlu");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    
    • 这种方式与源码中的使用文件流读取配置文件类似,源码使用的是FileInputStream来读取配置文件,最后都是使用的LogManager实例(该实例是单例)的对应方法加载输入流。

    image-20210301195930320

    image-20210301200126663

    • 配置文件中%h表示的是当前用户目录路径。

    OK,三种方式都可以进行自定义配置文件,其实也可以使用配置类进行来进行自定义配置,这里就不作描述。


    5.3、深入日志配置文件

    配置文件详解(含源码)*

    之前介绍了JDK默认的配置文件logging.properties,其中内容其实并不完整,本部分来完善相关的日志配置文件,并再去窥探源码看看在程序中是如何读取到配置文件中的配置信息的:

    # 1、handlers集合介绍
    # 1.1、handlers(无层级关系默认为RootLogger的handlers):是一个集合可设置多个handler
    handlers = java.util.logging.ConsoleHandler,java.util.logging.FileHandler
    # 默认为RootLogger的日志等级
    .level= ALL
    # 1.2、该handlers(包含层级关系,若程序中logger的name为xyz.changlu则默认为其添加handlers):
    #                          相当于对获取的logger实例添加自定义handlers集合
    xyz.changlu.handlers = java.util.logging.FileHandler
    # name为xyz.changlu的logger的日志等级
    xyz.changlu.level = ALL
    # name为xyz.changlu的logger忽略父日志设置
    xyz.changlu.useParentHandlers = false
    
    # 2、处理器设置
    # 2.1、ConsoleHandler(控制台处理器):设置等级为ALL
    java.util.logging.ConsoleHandler.level = ALL
    # 设置输出日志格式使用SimpleFormatter
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
    # 设置指定的编码格式
    java.util.logging.ConsoleHandler.encoding = UTF-8
    # 2.2、FileHandler(文件处理器):设置日志等级为ALL
    java.util.logging.FileHandler.level = ALL
    # 设置输出日志文件路径:%h表示家目录  %u与下面count有关(范围为[0,count))
    java.util.logging.FileHandler.pattern = %h/logs/java%u.log
    # 输出日志文件限制大小(50000字节)
    java.util.logging.FileHandler.limit = 50000
    # 输出日志文件限制个数
    java.util.logging.FileHandler.count = 1
    # 设置输出日志格式使用SimpleFormatter
    java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
    # 输出的日志内容是否追加到文件
    java.util.logging.FileHandler.append = true
    
    # 3、指定转换器的日志格式
    # 3.1、指定SimpleFormatter的转换形式 该format输入内容为 如WARNING: warning message [Tue Mar 22 13:11:31 PDT 2011]  对比以前少了指定调用方法
    java.util.logging.SimpleFormatter.format = "%4$s: %5$s [%1$tc]%n"
    
    # 设置name=com.xyz.foo的logger实例的日志等级(可省略不看)
    com.xyz.foo.level = SEVERE
    
    • 对于RootLogger的日志等级、指定handlers集合以及为自定义logger设置handlers以及日志等级;
    • 针对于不同处理器进行设置日志等级,其中FileHandler设置较多,包含日志等级、路径、文件大小、日志文件限制个数、转换各时期、是否追加。
    • 对于SimpleFormatter也可进行转换格式设置,设置formatter参数。

    该配置文件又补充了不少内容,那么补充的依据是在哪里呢?我们去源码中一探究竟:

    首先看下ConsoleHandler类:其在初始化构造器时就进行了一系列的操作

    public class ConsoleHandler extends StreamHandler {
    	//1、无参构造
    	public ConsoleHandler() {
            sealed = false;
            configure();//进行配置 去到2
            setOutputStream(System.err);
            sealed = true;
        }
        
        //2、进行配置操作
        private void configure() {
            LogManager manager = LogManager.getLogManager();//在此之间该LogManager的单例已经加载了自定义配置文件或官方提供的配置文件
            //cname就是获取本身的类名,为java.util.logging.ConsoleHandler
            String cname = getClass().getName();
    		//重要!!!!
            //setLevel()中的方法意思是,首先会去该LogManager实例的getProperty()方法获取对应的值(实际上就是使用properties实例获取键值对中的值),若是没有获取到就使用第二个参数默认设置为INFO
            setLevel(manager.getLevelProperty(cname +".level", Level.INFO));
            //针对于Filter若是从properteis中找不到对应的键值对,则默认设置filter为null
            setFilter(manager.getFilterProperty(cname +".filter", null));
            //若是找不到键值对,则默认设置转换器为SimpleFormatter
            setFormatter(manager.getFormatterProperty(cname +".formatter", new SimpleFormatter()));
            try {
                //同理,之类若是没有设置编码格式,则会设置为null
                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;
                }
            }
        }
    }
    
    • 实际上就是配置文件中对应的java.util.logging.ConsoleHandler.xxx的值,所以我们要记住了当我们进行自定义配置文件之后若是对对应handler进行了设置,那么在之后初始化handler实例也会受到影响!!!

    相对于FileHandler也同样如此,我直接放相应代码即可:

    //同样无参构造中调用了configure()方法
    private void configure()() {
        LogManager manager = LogManager.getLogManager();
    
        //cname = java.util.logging.FileHandler
        String cname = getClass().getName();
    
        //若是LogManager实例中的properties实例若是读取不到指定键值对java.util.logging.FileHandler.pattern,默认路径为%h/java%u.log
        pattern = manager.getStringProperty(cname + ".pattern", "%h/java%u.log");
        //若是读取不到java.util.logging.FileHandler.limit,默认为0
        limit = manager.getIntProperty(cname + ".limit", 0);
        if (limit < 0) {
            limit = 0;
        }
        //读取不到默认为1 (对应%u)
        count = manager.getIntProperty(cname + ".count", 1);
        if (count <= 0) {
            count = 1;
        }
        //append是是否追加的意思,默认若是没有配置文件为false,每次向文件写日志时就会进行覆盖,所以一般会设置为true
        append = manager.getBooleanProperty(cname + ".append", false);
        //对于FileHandler,若是没有自定义配置,会设置日志等级为ALL
        setLevel(manager.getLevelProperty(cname + ".level", Level.ALL));
        //对于过滤器,无自定义配置时默认为null
        setFilter(manager.getFilterProperty(cname + ".filter", null));
        //对于转换器,无自定义配置时默认为XMLFormatter(就是以XML形式保存,一般不这样)
        setFormatter(manager.getFormatterProperty(cname + ".formatter", new XMLFormatter()));
        try {
            //对于编码,无自定义配置时默认为null
            setEncoding(manager.getStringProperty(cname +".encoding", null));
        } catch (Exception ex) {
            try {
                setEncoding(null);
            } catch (Exception ex2) {
            }
        }
    }
    
    • ok,可以看到FileHandlerConsoleHandler的默认配置还有有差别的。对于日志等级FileHandler为ALL,而console为INFO;对于转换器formatter,FileHandler默认设置为XMLFormatter,console默认设置为SimpleFormatter

    最后看下转换器SimpleFormatter类:

    public class SimpleFormatter extends Formatter {
    	//这里是调用一个方法获取的,和之前的有些不一样,我们深入看一下  见1
    	private static final String format = LoggingSupport.getSimpleFormat()
    }
    
    //看一下LoggingSupport,粗略看下即可
    public class LoggingSupport {
        
        //1、LoggingSupport的静态方法
        public static String getSimpleFormat() {
            return getSimpleFormat(true);//见2
        }
        
        //2、注意这个方法中的操作与之前的就大致相同了!!!
        static String getSimpleFormat(boolean var0) {
            String var1 = (String)AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    //这里是尝试获取System类中props是否有该键值对,默认是没有的,返回null
                    return System.getProperty("java.util.logging.SimpleFormatter.format");
                }
            });
            //注意这里:若是参数var0为true&系统变量中没有读取到var1&代理类不为null(该代理类是static实例)
            //实际上这里调用代理类的getProperty()方法就是调用的LogManager.getLogManager().getProperty(key);
            //其实也就是之前LogManager读取的配置文件流!!!若是我们自定义了就会取到对应的值,没有的话返回null
            if (var0 && proxy != null && var1 == null) {
                var1 = proxy.getProperty("java.util.logging.SimpleFormatter.format");
            }
    
            //若var1不为null,表示从自定义配置文件中读取到了
            if (var1 != null) {
                try {
                    //格式化时会加上调用该方法的日期
                    String.format(var1, new Date(), "", "", "", "", "");
                } catch (IllegalArgumentException var3) {
                    var1 = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
                }
            } else {
                //自定义配置文件没有配置时采用该格式
                var1 = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n";
            }
            return var1;
        }
    }
    

    我们这边再看下proxy实例是怎么获取到的,以及调用的getProperty()到底是什么:

    image-20210302005349986

    最后对于该format的设置可以见源码中的注释,有是包含详细例子:

    image-20210302005608157

    • 三个方框里的是使用对应format打印出的例子。
    • 三个示例贴在这里:%4$s: %5$s [%1$tc]%n%1$tc %2$s%n%4$s: %5$s%6$s%n%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%n。其中符号含义可见源码或者api文档。


    小案例

    要求:在自定义配置文件中指定name=xyz.changlulogger实例(日志等级为ALL)包含一个FileHandler(限制大小为50000字节,文件数1个,路径指定D盘的los目录中,日志等级为ALL),该FileHandler对应一个SimpleFormatter(打印两行信息的格式),并且支持追加。

    :自定义配置文件如下:

    # 指定name为xyz.changlu的handlers集合中添加FileHandler
    xyz.changlu.handlers = java.util.logging.FileHandler
    # 该logger日志等级为ALL
    xyz.changlu.level = ALL
    # 表示不使用父元素的handlers集合(这里可不加)
    xyz.changlu.useParentHandlers = false
    
    # FileHandler的日志等级为ALL、路径、限制大小、数量、指定转换器、以及是否追加
    java.util.logging.FileHandler.level = ALL
    java.util.logging.FileHandler.pattern = D:/logs/java%u.log
    java.util.logging.FileHandler.limit = 50000
    java.util.logging.FileHandler.count = 1
    java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
    java.util.logging.FileHandler.append = true
    
    # SimpleFormatter中转换格式为堆栈方法
    java.util.logging.SimpleFormatter.format = "%1$tc %2$s%n%4$s: %5$s%6$s%n"
    
    • 这里我们没有指定rootloggerhandler,那么初始时自动创建的对应rootloggerhandlers则不会存在。

    注意点logs目录我们应当提前创建好,否则会报错。(若logs目录没有,则会出现找不到文件的异常)

    我们来验证一下结果:

    @Test
    public void test02() throws IOException {
        //读取配置文件输入流
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("logging.properties");
        //让LogManager来读取输入流
        LogManager.getLogManager().readConfiguration(is);
        
        //前两步骤做好之后再进行日志测试
        Logger logger = Logger.getLogger("xyz.changlu");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    

    image-20210302171734651

    • 屏幕前确实没有输出任何信息。

    image-20210302175050091

    • 成功打印到指定文件目录下的对应文件中。


    5.4、深入源码(日志执行流程,后)*

    之前5.1看的源码是关于配置文件相关的,这里的话来看一看日志执行的流程:

    //getlogger()方法读取了配置文件(前面5.1部分看的源码)
    Logger logger = Logger.getLogger("xyz.changlu");
    //本部分来看一下logger实例调用servere()方法中的执行过程
    logger.severe("severe");
    

    接下来跟着一起看下去吧:

    public class Logger {
        
        //1、该方法实际就是调用的log()方法,其第一个参数就是指定日志等级,很明显这里就是封装了一下变得更简洁
        public void severe(String msg) {
            log(Level.SEVERE, msg);//见2
        }
        
        //2、传入日志等级,以及日志要打印的msg参数
        public void log(Level level, String msg) {
            //重要①(比较自身logger实例的level),若是返回false,则该日志直接结束不进行下去
            if (!isLoggable(level)) {//见3
                return;
            }
            //将日志等级以及msg日志描述参数封装到LogRecord实例中
            LogRecord lr = new LogRecord(level, msg);
            doLog(lr);//见4
        }
        
        //3、对日志等级进行比较
        public boolean isLoggable(Level level) {
            //实例本身日志等级比当前要打印的日志等级大或者实例本身日志等级为最大情况下返回false
          if (level.intValue() < levelValue || levelValue == offValue) {//offValue的value为Integer.MAX_VALUE
                return false;
          }
          return true;
        }
        
        //4、传入封装好日志等级以及msg的lr
        private void doLog(LogRecord lr) {
            lr.setLoggerName(name);
            final LoggerBundle lb = getEffectiveLoggerBundle();
            final ResourceBundle  bundle = lb.userBundle;
            final String ebname = lb.resourceBundleName;
            if (ebname != null && bundle != null) {
                lr.setResourceBundleName(ebname);
                lr.setResourceBundle(bundle);
            }
            //上面先省略,继续看该方法
            log(lr);//见5
        }
        
        //5、该方法是执行过程中很重要的一部分
        public void log(LogRecord record) {
            //这里再次比较logger实例的日志等级以及本次日志打印的日志等级(有疑惑之前不是已经比较过了嘛)
            if (!isLoggable(record.getLevel())) {
                return;
            }
            //重要②,这里filter是本logger实例的filter过滤器
            Filter theFilter = filter;
            //重要②,若过滤器为不为null同时该过滤器的方法执行返回为false时直接结束方法
            if (theFilter != null && !theFilter.isLoggable(record)) {
                return;
            }
            
            //获取本身logger实例
            Logger logger = this;
            //接着开始执行logger实例中的handlers了
            while (logger != null) {
                //在Logger(String name)中设置isSystemLogger为true了,将原本使用List存储的handlers转为一个数组形式
                final Handler[] loggerHandlers = isSystemLogger
                    ? logger.accessCheckedHandlers()
                    : logger.getHandlers();
    			//重要③:遍历handlers数组
                for (Handler handler : loggerHandlers) {
                    //重要④:这一步实际上就是使用handler进行输出日志msg信息
                    handler.publish(record);//去6
                }
    
                //若是useParentHandlers为true将该布尔值给一个临时变量
                final boolean useParentHdls = isSystemLogger
                    ? logger.useParentHandlers
                    : logger.getUseParentHandlers();
    			//若是useParentHdls为false(表示不获取其父handler),那么就不会往下执行,整个log()方法结束
                if (!useParentHdls) {
                    break;
                }
    			//获取到上级logger(若是没有定义多个有层级关系的logger,则为rootlogger)
                //重新执行比对一遍logger的handlers集合中的日志等级及过滤器
                logger = isSystemLogger ? logger.parent : logger.getParent();
            }
        }
        
    }
    
    //假定当前的handler为ConsoleHandler(几个handler执行过程大致相同)
    public class ConsoleHandler extends StreamHandler {
        
        //6、输出方法
        @Override
        public void publish(LogRecord record) {
            //调用的是父类StreamHandler的publish()方法
            super.publish(record);//去7
            //刷新字符流
            flush();
        }
    }
    
    //Handler的父类StreamHandler
    public class StreamHandler extends Handler {
        
        //7、StreamHandler的publish()方法
        @Override
        public synchronized void publish(LogRecord record) {
            //重要⑤:比对指定handler的日志等级与logger实例的日志等级
            if (!isLoggable(record)) {//去8
                return;
            }
            //若是前面都顺利通过来到这里
            String msg;
            try {
                //使用logger实例指定的转换器进行格式转换
                msg = getFormatter().format(record);
            } catch (Exception ex) {
                reportError(null, ex, ErrorManager.FORMAT_FAILURE);
                return;
            }
    
            try {
                if (!doneHeader) {
                    writer.write(getFormatter().getHead(this));
                    doneHeader = true;
                }
                //最终打印出日志信息(根据handler决定去处)
                writer.write(msg);
            } catch (Exception ex) {
                reportError(null, ex, ErrorManager.WRITE_FAILURE);
            }
        }
        
        //8、这里有判断record与输出流是否为null
        @Override
        public boolean isLoggable(LogRecord record) {
            if (writer == null || record == null) {
                return false;
            }
            return super.isLoggable(record);//去9 (Handler类中的方法)
        }
    }
    
    //StreamHandler的父类Handler
    public abstract class Handler {
        
        private static final int offValue = Level.OFF.intValue();
        
        //9、这里才是真正比较handler的日志等级与logger实例的日志等级
        public boolean isLoggable(LogRecord record) {
            //levelValue默认为ALL,若是读取官方配置文件按配置文件的日志等级为INFO(这里默认读取官方配置为文件)
            final int levelValue = getLevel().intValue();
            //若是logger实例的日志等级为小于INFO或当前levelValue为Integer.MAX_VALUE,则结束方法回退到7
            if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
                return false;
            }
            //若是日志等级通过,在调用执行handler的filter过滤器进行比对
            final Filter filter = getFilter();
            if (filter == null) {
                //若是无过滤器直接通过
                return true;
            }
            //指定filter方法返回filter的结果
            return filter.isLoggable(record);
        }
    
    • 其实主要关键的看第3、5、7、9方法,最主要的是第5方法(核心调用)

    这部分源码的执行过程以及之前读取配置文件放在下个部分统一总结,好好理清一下思路。



    六、总结

    五章节前后源码总结*

    读取配置文件部分总结

    当调用Logger.getLogger()获取logger实例时,该方法中间进行了读取配置文件的操作:

    1. 读取自定义配置类。首先会去看System类中是否包含java.util.logging.config.class对应键值对的值,若是有会对该配置类进行实例化,直接结束配置读取。
    2. 读取自定义配置文件。会去看有没有java.util.logging.config.file对应的配置文件路径,有的话赋值到String fname中。
    3. 读取官方默认的配置文件(/jre/lib/logging.properties)。若是fame没有说明没有对应的配置文件路径,就会默认去找到java配置的路径,获取到完整路径同样赋值到String fname中。
      • 无论执行2或3步骤都是得到的一个fname全路径地址,使用FileInputStream将对应配置文件转为输入流,再统一使用LogManager中的properties实例的load()方法加载该输入流。

    若不自定义配置文件以及配置类时,会默认读取官方的配置文件

    • 该方法生成实例logger包含内容如下:
      • level=INFO(默认是使用的rootlogger的日志等级)
      • parent=rootlogger(根logger),其属性如下:
        • handlers只含ConsoleHandler
        • level=INFO
    • ConsoleHandler属性设置:level=INFOformatter=SimpleFormatter
    • FileHandler属性设置:pattern=%h/java%u.loglimit=50000count = 1formatter=XMLFormatter

    注意:当对应的handlerformatter初始化时会根据配置文件中的属性进行设置的。


    执行流程总结

    当logger实例调用severe(msg)或其他等级方法或log(level,msg)时,就会执行该流程:

    1. 首先比对的是logger实例的level,参数传入的level一定要比其>=才可继续执行。
    2. 执行logger实例的中filter过滤器,若是无过滤器或该过滤器方法返回false继续执行。
    3. 开始遍历logger实例中的handlers数组(list转数组)。
      • 每一个handler都有重要的两步,①传入参数level比对该handlerlevel,只有>=才可继续执行;②执行该handler中的fiter,若为null或执行fliter返回false继续执行。两步都通过就会进行输出日志(根据handler输出指向)。
    4. 接着判断logger实例的useParentHandlers这个布尔参数,若是为true,则获取到父级logger,遍历父级loggerhandlers,与第3步相同。

    默认情况下logger实例的levelINFOrootloggerlevelINFO(其中handler的等级为INFO)。



    参考资料

    视频:2020年Java进阶教程,全面学习多种java日志框架

    [1]. Java日志——JUL(java.util.logging)的使用及原理

    [2]. 日志处理器——Java日志 解决了我setUseParentHandlers(false)疑惑

    [3]. Java Logging之JUL系列——Log Levels 这个博主出了jul一个系列,在java栏目中很有帮助

    [4]. Java Logging之JUL系列——Handler

    [5]. Java Logging之JUL系列——Logger Hierarchy Logger的层级关系

    [6]. Java中getResourceAsStream的用法 用来读取日志配置文件

    [7]. System.getProperty()-获取系统参数 System.getProperty()-获取系统参数

    [8]. java.util.logging.LogManager 源码中java.util.logging.config.class以及java.util.logging.config.file对应用途

    [9]. JUL(java.util.logging)java原生官方日志 使用与配置–解决jul不输出显示日志问题

    [10]. java.util.logging使用笔记2 类中使用方法

    [11]. 日志框架1—多种日志门面和日志实现初步了解



    我是长路,感谢你的耐心阅读,如有问题请指出,我会听取建议并进行修正。
    欢迎关注我的公众号:长路Java,其中会包含软件安装等其他一些资料,包含一些视频教程以及学习路径分享。
    编程学习qq群:891507813 我们可以一起探讨学习
    注明:转载可,需要附带上文章链接

    展开全文
  • 简单建立数据库连接池及简单包装查询代码 使用JDK自带日志输出日志
  • 简单的建立数据连接池并包装查询代码及JDK自带日志使用
  • JDK自带日志Logging

    万次阅读 2016-02-22 16:36:19
    OK,现在我们来研究下JDK自带日志Logger。 从jdk1.4起,JDK开始自带一套日志系统。JDK Logger最大的优点就是不需要任何类库的支持,只要有Java的运行环境就可以使用。 相对于其他的日志空间,JDK自带日志可谓...


    OK,现在我们来研究下JDK自带的日志Logger。


    从jdk1.4起,JDK开始自带一套日志系统。JDK Logger最大的优点就是不需要任何类库的支持,只要有Java的运行环境就可以使用。
    相对于其他的日志空间,JDK自带的日志可谓是鸡肋,无论易用性,功能还是扩展性都要稍逊一筹,所以在商业系统中很好直接使用。
    我们现在整理到日志系列了,所以这里只是做一个简单的介绍就好了,比较实际编码中也不会写到这部分代码。

    JDK Logging把日志分为如下几个级别,等级依次升高。

    all→finest→finer→fine→config→info→warning→server→off

    如果将级别设为info,那么info值钱的低级别信息将不会输出,只有info级别只有的信息会输出,通过控制级别达到控制输出的目的。
    具体看下面的代码:

    package org.linkinpark.commons.logtest;
    
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月22日
     * @功能描述: JDK自带日志测试
     */
    public class LogJDKTest
    {
    	public static Logger log = Logger.getLogger(LogJDKTest.class.toString());
    
    	public static void main(String[] args)
    	{
    		// all→finest→finer→fine→config→info→warning→server→off
    		// 级别依次升高,后面的日志级别会屏蔽之前的级别
    		log.setLevel(Level.INFO);
    		log.finest("finest");
    		log.finer("finer");
    		log.fine("fine");
    		log.config("config");
    		log.info("info");
    		log.warning("warning");
    		log.severe("server");
    	}
    
    }
    
    控制台输出如下:

    二月 22, 2016 4:31:12 下午 org.linkinpark.commons.logtest.LogJDKTest main
    信息: info
    二月 22, 2016 4:31:12 下午 org.linkinpark.commons.logtest.LogJDKTest main
    警告: warning
    二月 22, 2016 4:31:12 下午 org.linkinpark.commons.logtest.LogJDKTest main
    严重: server
    

    1,JDK log默认会有一个控制台输出,用来输出INFO级别以上的信息,当然我们也可以调用logger的setLevel()方法来设值。

    2,同时我们也可以设置多个输出(Hander),每个输出设置不用的level,然后通过addHandler添加到了log中,注意为log设置级别与为每个handler设置级别的意义是不同的。

    代码如下:

    package org.linkinpark.commons.logtest;
    
    import java.util.logging.ConsoleHandler;
    import java.util.logging.Handler;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月22日
     * @功能描述: JDK自带日志测试
     */
    public class LogJDKTest
    {
    	public static Logger log = Logger.getLogger(LogJDKTest.class.toString());
    
    	static
    	{
    		Handler console = new ConsoleHandler();
    		console.setLevel(Level.SEVERE);
    		log.addHandler(console);
    	}
    
    	public static void main(String[] args)
    	{
    		// all→finest→finer→fine→config→info→warning→server→off
    		// 级别依次升高,后面的日志级别会屏蔽之前的级别
    		log.setLevel(Level.INFO);
    		log.finest("finest");
    		log.finer("finer");
    		log.fine("fine");
    		log.config("config");
    		log.info("info");
    		log.warning("warning");
    		log.severe("server");
    	}
    
    }
    
    控制台输出如下:

    // 下面的输出是默认控制台的
    二月 22, 2016 4:32:41 下午 org.linkinpark.commons.logtest.LogJDKTest main
    信息: info
    二月 22, 2016 4:32:41 下午 org.linkinpark.commons.logtest.LogJDKTest main
    警告: warning
    二月 22, 2016 4:32:41 下午 org.linkinpark.commons.logtest.LogJDKTest main
    严重: server
    // 下面的输出是自己添加hander的
    二月 22, 2016 4:32:41 下午 org.linkinpark.commons.logtest.LogJDKTest main
    严重: server
    


    根据级别等级,如果设为all,则所有的信息都会被输出,如果设为off,则所有的信息都不会输出。


    • 这里贴出JDK关于该类的介绍

    =====================================================================================================================================================================

    Logger 对象用来记录特定系统或应用程序组件的日志消息。一般使用圆点分隔的层次名称空间来命名 Logger。Logger 名称可以是任意的字符串,但是它们一般应该基于被记录组件的包名或类名,如 java.net 或 javax.swing。此外,可以创建“匿名”的 Logger,其名称未存储在 Logger 名称空间中。

    可通过调用某个 getLogger 工厂方法来获得 Logger 对象。这些方法要么创建一个新 Logger,要么返回一个合适的现有 Logger。

    日志消息被转发到已注册的 Handler 对象,该对象可以将消息转发到各种目的地,包括控制台、文件、OS 日志等等。

    每个 Logger 都跟踪一个“父”Logger,也就是 Logger 名称空间中与其最近的现有祖先。

    每个 Logger 都有一个与其相关的 "Level"。这反映了此 logger 所关心的最低 Level。如果将 Logger 的级别设置为 null,那么它的有效级别继承自父 Logger,这可以通过其父 Logger 一直沿树向上递归得到。

    可以根据日志配置文件的属性来配置日志级别,在 LogManager 类的描述中对此有所说明。但是也可以通过调用 Logger.setLevel 方法动态地改变它。如果日志级别改变了,则此变化也会影响它的子 logger,因为任何级别为 null 的子 logger 的有效级别都继承自它的父 Logger。

    对于每次日志记录调用,Logger 最初都依照 logger 的有效日志级别对请求级别(例如 SEVERE 或 FINE)进行简单的检查。如果请求级别低于日志级别,则日志记录调用将立即返回。

    通过此初始(简单)测试后,Logger 将分配一个 LogRecord 来描述日志记录消息。接着调用 Filter(如果存在)进行更详细的检查,以确定是否应该发布该记录。如果检查通过,则将 LogRecord 发布到其输出 Handler。在默认情况下,logger 也将 LogRecord 沿树递推发布到其父 Handler。

    每个 Logger 都有一个与其关联的 ResourceBundle 名称。该指定的包用于本地化日志消息。如果一个 Logger 没有自己的 ResourceBundle 名称,则它将通过其父 Logger 沿树递归继承到 ResourceBundle 名称。

    大多数 logger 输出方法都带有 "msg" 参数。此 msg 参数可以是一个原始值,也可以是一个本地化的键。在格式化期间,如果 logger 具有(或继承)一个本地化 ResourceBundle,并且 ResourceBundle 包含 msg 字符串的映射关系,那么用本地化值替换 msg 字符串。否则使用原来的 msg 字符串。通常,格式器使用 java.text.MessageFormat 形式的格式来格式化参数,例如,格式字符串 "{0} {1}" 将两个参数格式化为字符串。

    将 ResourceBundle 名称映射到 ResourceBundle 时,Logger 首先试图使用该线程的 ContextClassLoader。如果 ContextClassLoader 为 null,则 Logger 将尝试 SystemClassLoader。作为初始实现中的临时过渡功能,如果 Logger 无法从 ContextClassLoader 或 SystemClassLoaderis 中找到一个 ResourceBundle,则 Logger 将会向上搜索类堆栈并连续调用 ClassLoader 来试图找到 ResourceBundle(此调用堆栈搜索是为了允许容器过渡到使用 ContextClassLoader,该功能可能在以后版本中取消)。

    格式化(包括本地化)是输出 Handler 的责任,它通常会调用格式器。

    注意,格式化不必同步发生。它可以延迟,直到 LogRecord 被实际写入到外部接收器。

    日志记录方法划分为 5 个主要类别:

    • 一系列的 "log" 方法,这种方法带有日志级别、消息字符串,以及可选的一些消息字符串参数。

    • 一系列的 "logp" 方法(即 "log precise"),其与 "log" 方法相似,但是带有显式的源类名称和方法名称。

    • 一系列的 "logrb" 方法(即 "log with resource bundle"),其与 "logp" 方法相似,但是带有显式的在本地化日志消息中使用的资源包名称。

    • 还有跟踪方法条目("entering" 方法)、方法返回("exiting" 方法)和抛出异常("throwing" 方法)的便捷方法。

    • 最后,还有一系列在非常简单的情况下(如开发人员只想为给定的日志级别记录一条简单的字符串)使用的便捷方法。这些方法按标准级别名称命名("severe"、"warning"、"info" 等等),并带有单个参数,即一个消息字符串。

    对于不带显式源名和方法名的方法,日志记录框架将尽可能确定日志记录方法中调用了哪个类和方法。但是应认识到,这样自动推断的信息可能只是近似的,甚至可能是完全错误的。这是因为允许虚拟机在 JIT 编译时可以进行广泛的优化,并且可以完全移除栈帧,导致它无法可靠地找到调用的类和方法。

    Logger 上执行的所有方法都是多线程安全的。

    子类化信息:注意,对于名称空间中的任意点,LogManager 类都可以提供自身的指定 Logger 实现。因此,Logger 的任何子类(它们与新的 LogManager 类一起实现的情况除外)要注意应该从 LogManager 类获得一个 Logger 实例,并应该将诸如 "isLoggable" 和 "log(LogRecord)" 这样的操作委托给该实例。注意,为了截取所有的日志记录输出,子类只需要重写 log(LogRecord) 方法。所有其他日志记录方法作为在此 log(LogRecord) 方法上的调用而实现。


    ======================================================= ======================================================= =======================================================
    展开全文
  • jdklogging.rar,MyConsoleHandler.java,LogClient.java,MySimpleFormatter.java
  • JDK自带日志工具Logger的研究使用

    万次阅读 2016-10-11 00:53:20
    最近放假在家无聊,研究一个...点开源码看,才发现是JDK自带日志类,非第三方开源Jar包,于是便起了好奇之心,想看看这个Logger与log4j和commons-logging有何不同,翻了翻Blog,使用研究一番,此处说下我的初步体验。
  • 作者简介 作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通Java、JavaScript,博主也是从零开始一步步把学习成长、深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢迎您关注,期待与您一起...
  • 1.简单使用 1.1 示例代码 private static final Logger logger=Logger.... logger.info("jdk logging info: a msg"); } 其中的Logger是:java.util.logging.Logger 1.2 过程分析 1)创建一个LogManager,默认是ja
  • 版权声明:本文为Jaiky_杰哥原创,转载请... https://blog.csdn.net/jaikydota163/article/details/52783588关于Logger最近放假在家无聊,研究一个开源框架时发现它频繁运用到了一个叫Logger的相关类来进行日志记录...
  • 下面代码对JDK内置的log简单地封装了一下。直接复制到项目即可用。更不需要maven。 使用方法看main函数。 import java.io.PrintWriter; import java.io.StringWriter; import java.text.SimpleDateFormat; import...
  • JDK日志分级

    2021-03-08 09:32:41
    日志(Log)是什么?字典对其的解释是"对某种机器工作情况或某项任务进展情况的记载"。对于应用系统来说,日志就应该记录应用系统的运行状况了。是否需要记录日志?这个问题无需回答,这是毋庸置疑的--当然要记了。...
  • JDK自带的HTTP利器:HttpURLConnection

    千次阅读 2020-08-18 20:36:06
    JDK的java.net包中已经提供了访问HTTP协议的基本功能的类:HttpURLConnection。 HttpURLConnection是Java的标准类,它继承自URLConnection,可用于向指定网站发送GET请求、POST请求。它在URLConnection的基础上...
  • 1.jconsole jconsole 主要监控 JVM 的概览、内存、线程、类、vm概要、MBean等内容。JConsole 会消耗大量系统资源,因此 Oracle 建议仅在用于创建原型的开发环境中使用...jvisualvm,jdk 自带全能工具,可以分析内存..
  • * 日志记录功能 * @author WJL * @date 2014-1-22 * @email wjl@zving.com */ public class WebLogger { private static Logger logger = Logger.getLogger(WebLogger.class.getName()); static{ try { ...
  • 前言 通过我之前的文章《JVM堆内存模型概括》、《java垃圾回收机制概括》、《JVM怎样判断是垃圾对象进行垃圾回收》、《eclipse怎么设置堆内存大小》...这里我们就需要用到一个jdk自带的工具了,下面我来讲解一下工具
  • 下面是JDK自带的HttpServer处理Http请求的源码和流程,我看网上貌似还没有介绍这个的流程,所有就画了一下,如有不足,请矫正。 1、官方API ... ... privatestaticfinalMap<String, Htt...
  • Java日志--jdk自带log

    2020-03-05 19:30:26
    名词解释:jul(java.util.logging)起始版本:从jdk1.4及之后都有java.util.logging 示例 java程序 import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class ...
  • JDK自带工具包的使用

    千次阅读 2020-04-14 23:09:02
    常用jdk工具(命令行)2.1 jps(JVM Process Status Tool)2.2 jstat(JVM Statistics Monitoring Tool)2.3 jinfo(Configuration Info forJava)2.4 jmap(Memory Map for Java)2.5 jhat(JVM Heap Dump Browser)2.6 js...
  • Java自带log功能,用的是JDK标准库中的类java.util.logging.Logger 。下面是一个简单的例子: package pkg1; import java.util.logging.*; public class Test0509 { public static void main(String[] args) { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 37,725
精华内容 15,090
关键字:

jdk自带的日志