Java日志框架学习--JUL和Log4j--上

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java日志框架学习--JUL和Log4j--上相关的知识,希望对你有一定的参考价值。

Java日志框架学习--JUL和Log4j--上


引言

日志框架

1.控制日志输出的内容和格式。

2.控制日志输出的位置。

3.日志文件相关的优化,如异步操作、归档、压缩…

4.日志系统的维护

5.面向接口开发 – 日志的门面


市面流行的日志框架

JUL java util logging

  • Java原生日志框架,亲儿子

Log4j

  • Apache的一个开源项目

Logback

  • 由Log4j之父做的另一个开源项目
  • 业界中称作log4j后浪
  • 一个可靠、通用且灵活的java日志框架

Log4j2

  • Log4j官方的第二个版本,各个方面都是与Logback及其相似
  • 具有插件式结构、配置文件优化等特征
  • Spring Boot1.4版本以后就不再支持log4j,所以第二个版本营运而生

JCL

SLF4j


日志门面和日志框架的区别

日志框架技术 JUL、Logback、Log4j、Log4j2

用来方便有效地记录日志信息

日志门面技术 JCL、SLF4j

为什么要使用日志门面技术:

每一种日志框架都有自己单独的API,要使用对应的框架就要使用对应的API,这就大大的增加了应用程序代码对于日志框架的耦合性。

我们使用了日志门面技术之后,对于应用程序来说,无论底层的日志框架如何改变,应用程序不需要修改任意一行代码,就可以直接上线了。


JUL

JUL简介

JUL全称 Java Util Logging,它是java原生的日志框架,使用时不需要另外引用第三方的类库,相对其他的框架使用方便,学习简单,主要是使用在小型应用中。

JUL组件介绍

Logger:被称为记录器,应用程序通过获取Logger对象,使用其API来发布日志信息。Logger通常被认为是访问日志系统的入口程序。

Handler:处理器,每个Logger都会关联一个或者是一组Handler,Logger会将日志交给关联的Handler去做处理,由Handler负责将日志做记录。Handler具体实现了日志的输出位置,比如可以输出到控制台或者是文件中等等。

Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被略过。

Formatter:格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了我们输出日志最终的形式。

Level:日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,用来展现最终所呈现的日志信息。根据不同的需求,去设置不同的级别。


实际使用

public class JUITest 
    private final String NOW="当前时间: "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
    @Test
    public void normalTest()
        //获取Logger--传入当前类的全类名
        Logger logger = Logger.getLogger(JUITest.class.getName());
        //info级别输出日志
        logger.info(NOW);
        logger.log(Level.INFO,NOW);
        //占位符传参--0和1必须指定,否则占位符不生效
        logger.log(Level.INFO,"管理员姓名为: 0, 年龄为1",new Object[]"大忽悠",18);
        //输出不同的日志级别--默认为info,因此只会输出比info级别高的日志信息,包括info
         logger.severe("severe");
         logger.warning("warning");
         logger.info("info");
         //------------默认输出上面三个-------------
         logger.fine("fine");
         logger.finer("finer");
         logger.finest("finest");
    


    @Test
    public void changeLevelTest()
        Logger logger = Logger.getLogger(JUITest.class.getName());
        //将默认的日志打印方式关闭掉,这样日志打印方式就不会按照父logger默认的方式去进行操作
        logger.setUseParentHandlers(false);
        //使用控制台处理器
        ConsoleHandler handler = new ConsoleHandler();
       //创建日志格式化组件
        SimpleFormatter formatter = new SimpleFormatter();
        //设置处理器中日志输出格式
        handler.setFormatter(formatter);
        //在记录器中添加处理器
        logger.addHandler(handler);
        //设置日志级别
        //此处必须将日志记录器和处理器的基本进行统一的设置,才会达到日志显示相应级别的效果
        logger.setLevel(Level.ALL);
        handler.setLevel(Level.ALL);

        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    

    /**
     * handler可以同时添加多个
     */
    @Test
    public void logToFile() throws IOException 
        Logger logger = Logger.getLogger(JUITest.class.getName());
        logger.setUseParentHandlers(false);
        ConsoleHandler consoleHandler = new ConsoleHandler();
        FileHandler fileHandler = new FileHandler("test.log");
        SimpleFormatter formatter = new SimpleFormatter();
        consoleHandler.setFormatter(formatter);
        fileHandler.setFormatter(formatter);

        logger.addHandler(consoleHandler);
        logger.addHandler(fileHandler);

        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);
        fileHandler.setLevel(Level.INFO);

        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    



给出的是最后一个测试的结果

真正完成日志记录的源码如下,其实很简单:

    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方法,也会去判断当前日志级别,然后拿到格式化器,最终进行输出
                handler.publish(record);
            

            final boolean useParentHdls = isSystemLogger
                ? logger.useParentHandlers
                : logger.getUseParentHandlers();

            if (!useParentHdls) 
                break;
            

            logger = isSystemLogger ? logger.parent : logger.getParent();
        
    

不同handler的相同抽象publish的核心逻辑

    @Override
    public synchronized void publish(LogRecord record) 
        if (!isLoggable(record)) 
            return;
        
        String msg;
        try 
            msg = getFormatter().format(record);
         catch (Exception ex) 
            // We don't want to throw an exception here, but we
            // report the exception to any registered ErrorManager.
            reportError(null, ex, ErrorManager.FORMAT_FAILURE);
            return;
        

        try 
            if (!doneHeader) 
                writer.write(getFormatter().getHead(this));
                doneHeader = true;
            
            writer.write(msg);
         catch (Exception ex) 
            // We don't want to throw an exception here, but we
            // report the exception to any registered ErrorManager.
            reportError(null, ex, ErrorManager.WRITE_FAILURE);
        
    

writer可以是输出向控制台,也可以是文件。

    private volatile Writer writer;

Logger之间的父子关系

    @Test
    public void testParentLogger()
        //父亲是RootLogger,名称默认是一个空的字符串,RootLogger可以被称之为所有Logger对象的顶层Logger
        Logger logger1 = Logger.getLogger("helper.com.logTest");
        Logger logger2 = Logger.getLogger("helper.com.logTest.JUITest");
        System.out.println("log1的父log引用为: "+logger1.getParent()+" ; 名称为: "+logger1.getName()+
                "  父亲的名称为: "+logger1.getParent().getName());
        System.out.println("log2的父log引用为: "+logger2.getParent()+" ; 名称为: "+logger2.getName()+
                "  父亲的名称为: "+logger2.getParent().getName());
    


父亲的设置会影响到儿子,也可以按照包的角度来理解,我可以设置整个包的日志属性,也可以定制化包下某个类的日志属性

    @Test
    public void testParentLogger()
        Logger logger1 = Logger.getLogger("helper.com.logTest");
        logger1.setUseParentHandlers(false);
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.ALL);
        logger1.addHandler(handler);
        logger1.setLevel(Level.ALL);

        Logger logger2 = Logger.getLogger("helper.com.logTest.JUITest");
        logger2.severe("severe");
        logger2.warning("warning");
        logger2.info("info");
        logger2.fine("fine");
        logger2.finer("finer");
        logger2.finest("finest");
    

logger2默认为info级别,但是因为我们设置了它的父logger日志级别,所以这里按照父logger属性进行输出

底层通过一个保存的节点树,再创建每个logger的时候,通过节点树,找到其父节点

具体原理,可以参考这篇文章


默认配置文件位置

我们上面都是硬编码方式完成的,但是大部分情况下,都是通过配置文件完成的

Logger.getLogger方法会调用到ensureLogManagerInitialized方法,默认配置文件的加载在该方法中完成

final void ensureLogManagerInitialized() 
                ....
                        // Read configuration.--读取配置文件
                        owner.readPrimordialConfiguration();
                 ...
    
private void readPrimordialConfiguration() 
        if (!readPrimordialConfiguration) 
            synchronized (this) 
                if (!readPrimordialConfiguration) 
                    // If System.in/out/err are null, it's a good
                    // indication that we're still in the
                    // bootstrapping phase
                    if (System.out == null) 
                        return;
                    
                    readPrimordialConfiguration = true;

                    try 
                        AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() 
                                @Override
                                public Void run() throws Exception 
                                   //继续看
                                    readConfiguration();

                                    // 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;
                    
                
            
        
    
public void readConfiguration() throws IOException, SecurityException 
        checkPermission();

        // if a configuration class is specified, load it and use it.
        String cname = System.getProperty("java.util.logging.config.class");
        if (cname != null) 
            try 
                // Instantiate the named class.  It is its constructor's
                // responsibility to initialize the logging configuration, by
                // calling readConfiguration(InputStream) with a suitable stream.
                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.
            
        
       
       //查看是否指定了配置文件位置 
        String fname = System.getProperty("java.util.logging.config.file");
        if (fname == null) 
        //如果没有指定,会采用默认的配置文件
            fname = System.getProperty("java.home");
            if (fname == null) 
                throw new Error("Can't find java.home ??");
            
            File f = new File(fname, "lib");
            f = new File(f, "logging.properties");
            //默认配置文件为java_home目录下的lib目录下的logging.properties文件
            fname = f.getCanonicalPath();
        
        try (final InputStream in = new FileInputStream(fname)) 
            final BufferedInputStream bin = new BufferedInputStream(in);
            readConfiguration(bin);
        
    

默认配置文件如下:

#RootLogger使用的处理器
#如果想要添加其他的处理器,可以采用逗号分隔的形式,添加多个处理器
handlers= java.util.logging.ConsoleHandler

#默认RootLogger的日志级别
#全局日志级别
.level= INFO

#文件处理器属性设置
#输出日志文件路径设置
java.util.logging.FileHandler.pattern = %h/java%u.log
#输出日志文件的限制--字节
java.util.logging.FileHandler.limit = 50000
#输出日志文件的格式
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter


java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

#也可以将日志级别设置到具体的某个包下
com.xyz.foo.level = SEVERE


读取自定义配置文件

    @Test
    public void testParentLogger() throws IOException 
        LogManager logManager = LogManager.getLogManager();
        logManager.readConfiguration(new 

以上是关于Java日志框架学习--JUL和Log4j--上的主要内容,如果未能解决你的问题,请参考以下文章

实现jul 日志重定向到 slf4j

java项目日志系统的总结

springboot-日志框架

日志框架

Java日志框架 -- 日志框架介绍日志门面技术JUL日志(JUL架构JUL入门示例JUL日志级别JUL日志的配置文件)

日志框架总结