01、JUL日志(JDK自带日志框架,包含源码分析)(上)

举报
长路 发表于 2022/11/28 08:31:48 2022/11/28
【摘要】 文章目录前言一、JUL架构介绍1.1、认识不同组件1.2、Logger1.3、Handler二、输出日志信息三、自定义日志级别配置3.1、认识Level类3.2、输出不同等级日志3.2、自定义日志级别(console与文件输出)四、Logger的子父类关系4.1、认识根Logger4.2、Logger的info(msg)执行流程*4.3、设置日志等级五、日志的配置文件5.1、初探源码(读取配置文件

@[toc]

前言

在平时跟着教程做项目时,对于日志处理往往都是直接引入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,三种方式都可以进行自定义配置文件,其实也可以使用配置类进行来进行自定义配置,这里就不作描述。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。