美文网首页
Java日志框架JUL

Java日志框架JUL

作者: 贪睡的企鹅 | 来源:发表于2019-08-20 15:20 被阅读0次

1 日志实现原理

Java 的日志框架有很多,比如:JUL(Java Util Logging)、Log4j、Logback、Log4j2、Tinylog 等,但其核心功能,实现原理基本一致。

2.1 核心功能

日志系统核心时记录日志,以方便排查问题或作为其他系统进行统计。其核心功能如下

  • 1 支持多渠道输出

  • 2 日志信息支持多等级

  • 3 渠道,日志和等级做关联,以支持渠道过滤不必要的日志信息

2.2 架构设计

针对上述功能日志系统划分为如下模块:

image

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

Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。

Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。

Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,我可以将Level和Loggers,Appenders做关联以便于我们过滤消息。

2.3 处理流程

当Logger记录一个日志时,它将日志信息转发给适当的Appender。然后Appender使用Layout来对日志记录进行格式化,并将其发送给控制台、文件或者其它目标位置。

2 JUL

2.1 概述

JUL全称Java util Logger是java原生的日志框架,使用时不需要另外引用类库,相对其他日志框架方便,学习简单,能够在小型应用中灵活使用。

2.2 核心功能
2.2.1 Logger

JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。

        Logger logger = Logger.getLogger("com.wuhao.log");
        Logger logger1 = Logger.getLogger("com.wuhao");
        Logger logger2 = Logger.getLogger("com");
        System.out.println(logger);
        System.out.println(logger1.equals(logger.getParent()));
        System.out.println(logger2.equals(logger1.getParent()));

从上诉代码可以发现父Loggers并不是表示父类,Loggers类内部存在一个类型为Loggers属性parent用来表示父Loggers.

父Loggers存在意义在于复用处理器Handler,在默认情况Logger记录日志时,会判断是否存在父Loggers,如果存在那么不仅仅自己关联Handlers会处理,其父Loggers关联Handlers也会处理。当然我们也可以通过配置忽略父Logger来处理。

2.2.2 日志级别

每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫性,日志级别定义在java.util.logging.Level类中,用装整数值来表示,值越高表示优先级越高。

常用日志级别如下:

  • SEVERE(最高值)
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST(最低值)

还有两个特殊的级别

  • OFF,可用来关闭日志记录。
  • ALL,启用所有消息的日志记录。

logger默认的级别是INFO,比INFO更低的日志将不显示

2.2.3 Handler

JUL提供多种日志处理器。

  • StreamHandler:用于将格式化记录写入OutputStream的简单处理程序。
  • ConsoleHandler:用于将格式化记录写入System.err的简单处理程序
  • FileHandler:将格式化日志记录写入单个文件或一组旋转日志文件的处理程序。
  • SocketHandler:将格式化日志记录写入远程TCP端口的处理程序。
  • MemoryHandler:缓冲内存中日志记录的处理程序。
2.2.4 Formatter

JUL提供了2种日志格式处理器

  • SimpleFormatter:写简短的“人类可读”日志记录摘要。
  • XMLFormatter:写入详细的XML结构信息。
2.2.5 多样化记录日志API

Logger类提供了一组用于生成日志消息的便捷方法,为方便起见,每个日志记录级别都有记录方法API,方法名称以日志记录级别名称命名。这样就不再需要调用“logger.log(Level.WARNING,......”方式记录日志

调用相应级别的API方法

    public void warning(String msg) {
        log(Level.WARNING, msg);
    }

将级别作为参数传入来方法

    public void log(Level level, String msg) {
        if (!isLoggable(level)) {
            return;
        }
        LogRecord lr = new LogRecord(level, msg);
        doLog(lr);
    }

同时记录日志时可以告知记录日志的源类名和源方法名

oid warning(String sourceClass,String sourceMethod,String msg);
2.2.6 LogManager

LogManager 被称为Logger的管理器,当我们获取Logger本质是从LogManager获取,LogManager内部通过LoggerContext类对象来存储logger,并维护logger之间的父子关系。实现原理是将每个Looger封装为LogNode节点,存在在一个树状结构中。

2.2.7 配置文件

JUL读取指定路径下logging.properties文件来初始化LogManager,默认情况下配置文件路径为$JAVAHOME\jre\lib\logging.properties

2.3 使用案例

Logger之间的父子关系

    /**
     * 日志之间存在父子关系,最顶层的日志类型为LogManager$RootLogger,命名为""
     */
    @Test
    public void test() throws IOException {
        Logger logger = Logger.getLogger("com.wuhao.log");
        Logger logger1 = Logger.getLogger("com.wuhao");
        Logger logger2 = Logger.getLogger("com");
        System.out.println(logger);
        System.out.println(logger1.equals(logger.getParent()));
        System.out.println(logger2.equals(logger1.getParent()));
        System.out.println(logger2.getParent());
    }
    
java.util.logging.Logger@7fbe847c
true
true
java.util.logging.LogManager$RootLogger@41975e01

读取配置文件打印日志

@Test
    public void test2() throws IOException {
        InputStream in = JULTest.class.getResourceAsStream("/logging.properties");//注意配置
        logManager.readConfiguration(in);
        Logger logger= Logger.getLogger("TestLog");
         logger.info("INFO");
        Logger otherLog = Logger.getLogger("otherLog");
        otherLog.info("Other INFO");
    }

配置文件

## RootLogger处理器(获取时设置)
handlers= java.util.logging.ConsoleHandler
## RootLogger处理器(打印日志时设置)
.handlers= RootLogger.java.util.logging.FileHandler
# RootLogger日志等级
.level= INFO

## TestLog日志处理器
TestLog.handlers= java.util.logging.FileHandler
# TestLog日志等级
TestLog.level= INFO
# 忽略父日志处理
TestLog.useParentHandlers=false

## 控制台处理器
# 输出日志级别
java.util.logging.ConsoleHandler.level = INFO
# 输出日志格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter


## 文件处理器
# 输出日志级别
java.util.logging.FileHandler.level=INFO
# 输出日志格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 输出日志文件路径
java.util.logging.FileHandler.pattern = D:\\TestLog.log
# 输出日志文件限制大小(50000字节)
java.util.logging.FileHandler.limit = 50000
# 输出日志文件限制个数
java.util.logging.FileHandler.count = 10
# 输出日志文件 是否是追加
java.util.logging.FileHandler.append=true


## 文件处理器
# 输出日志级别
RootLogger.java.util.logging.FileHandler.level=INFO
# 输出日志格式
RootLogger.java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 输出日志文件路径
RootLogger.java.util.logging.FileHandler.pattern = D:\\RootLogger.log
# 输出日志文件限制大小(50000字节)
RootLogger.java.util.logging.FileHandler.limit = 50000
# 输出日志文件限制个数
RootLogger.java.util.logging.FileHandler.count = 10
# 输出日志文件 是否是追加
RootLogger.java.util.logging.FileHandler.append=true

3 原理解析

3.1 获取Logger

getLogger获取Logger的本质是从全局单例LogManager获取Logger。

    /** 单例LogManager **/
    private static final LogManager manager;


    /**
     * 获取Logger
     */
    @CallerSensitive
    public static Logger getLogger(String name) {
        return demandLogger(name, null, Reflection.getCallerClass());
    }
    
    /**
     * 从单例LogManager获取Logger
     */
    private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
        /** 获取单例LogManager **/
        LogManager manager = LogManager.getLogManager();
        SecurityManager sm = System.getSecurityManager();
        if (sm != null && !SystemLoggerHelper.disableCallerCheck) {
            if (caller.getClassLoader() == null) {
                return manager.demandSystemLogger(name, resourceBundleName);
            }
        }
        /** 从单例LogManager中获取Logger **/
        return manager.demandLogger(name, resourceBundleName, caller);
    }
    


    /**
     * 获取单例LogManager
     */
    public static LogManager getLogManager() {
        if (manager != null) {
            /** 单例LogManager初始化 **/
            manager.ensureLogManagerInitialized();
        }
        return manager;
    }
2.4.1 初始化LogManager
final void ensureLogManagerInitialized() {
        final LogManager owner = this;
        /** 如果当前对象不是单例LogManager或以被初始化直接返回 **/
        if (initializationDone || owner != manager) {
            return;
        }
        synchronized(this) {
            ...省略代码
            try {
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    @Override
                    public Object run() {
                        assert rootLogger == null;
                        assert initializedCalled && !initializationDone;
                        /** 单例LogManager读取 $JAVAHOME\jre\lib\logging.properties 配置文件加载到Properties类型的属性props中**/
                        owner.readPrimordialConfiguration();
                        /** 给LogManager添加RootLogger **/
                        owner.rootLogger = owner.new RootLogger();
                        owner.addLogger(owner.rootLogger);
                        /** 是否设置rootLogger日志级别**/
                        if (!owner.rootLogger.isLevelInitialized()) {
                            /** 设置RootLogger日志等级 **/
                            owner.rootLogger.setLevel(defaultLevel);
                        }
                        /** 给LogManager添加global Logger. **/
                        final Logger global = Logger.global;
                        owner.addLogger(global);
                        return null;
                    }
                });
            } finally {
                initializationDone = true;
            }
        }
    }

LogManager加载logging.properties配置

    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 {
                                    /** 读取默认logging.properties配置 **/
                                    readConfiguration();
                                   sun.util.logging.PlatformLogger.redirectPlatformLoggers();
                                    return null;
                                }
                            });
                    } catch (Exception ex) {
                        assert false : "Exception raised while reading logging configuration: " + ex;
                    }
                }
            }
        }
    }
    
    /**
     * 读取默认logging.properties配置
     */
    public void readConfiguration() throws IOException, SecurityException {
        checkPermission();
        /** 尝试获取系统属性java.util.logging.config.class对应日志配置实现类,并实例化对象 **/
        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);
            }
        }
        
       /** 
        * 从系统属性java.util.logging.config.file中获取默认配置文件路径 
        * 如果不存在则读取默认配置文件路径$JAVAHOME\jre\lib\logging.properties
        **/
        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");
            fname = f.getCanonicalPath();
        }
        /** 获取配置文件FileInputStream **/
        try (final InputStream in = new FileInputStream(fname)) {
            final BufferedInputStream bin = new BufferedInputStream(in);
            /** 读取InputStream配置信息 **/
            readConfiguration(bin);
        }
    }
    
    /**
     * 读取InputStream配置信息
     */
    public void readConfiguration(InputStream ins) throws IOException, SecurityException {
        checkPermission();
        /** 重置LogManager 内部调用resetLogger方法重置所有被LogManager管理的Logger**/
        reset();

        /**  将配置文件信息加载到props **/
        props.load(ins);

        /**  解析配置中config属性值,将","分隔字符串分隔成字符串数组**/
        String names[] = parseClassNames("config");

        /** 尝试实例化config标识配置类对象 **/
        for (int i = 0; i < names.length; i++) {
            String word = names[i];
            try {
                Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word);
                clz.newInstance();
            } catch (Exception ex) {
                System.err.println("Can't load config class \"" + word + "\"");
                System.err.println("" + ex);
                // ex.printStackTrace();
            }
        }

        /**
         * 读取配置文件中 .level结尾的配置,设置对应Logger的日志等级
         * test.level=INFO 标识test Loggr的日志等级为INFO
         * **/
        setLevelsOnExistingLoggers();

        /** 为LogManager设置属性监听器 **/
        Map<Object,Integer> listeners = null;
        synchronized (listenerMap) {
            if (!listenerMap.isEmpty())
                listeners = new HashMap<>(listenerMap);
        }
        if (listeners != null) {
            assert Beans.isBeansPresent();
            Object ev = Beans.newPropertyChangeEvent(LogManager.class, null, null, null);
            for (Map.Entry<Object,Integer> entry : listeners.entrySet()) {
                Object listener = entry.getKey();
                int count = entry.getValue().intValue();
                for (int i = 0; i < count; i++) {
                    Beans.invokePropertyChange(listener, ev);
                }
            }
        }

        /** 标识还未设置全局Handlers **/
        synchronized (this) {
            initializedGlobalHandlers = false;
        }
    }

添加Logger到LogManager

LogManager内部通过LoggerContext类对象来存储logger,并维护logger之间的父子关系。实现原理是将每个Looger封装为LogNode节点,存在在一个树状结构中。

private static class LogNode {
        /** 子节点 **/
        HashMap<String,LogNode> children;
        /** 关联Logger引用对象 **/
        LoggerWeakRef loggerRef;
        /** 父节点**/
        LogNode parent;
        /** 关联的LoggerContext **/
        final LoggerContext context;

class LoggerContext {
        /** 存储LoggerContext中保存的所有Logger引用对象 **/.
        private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();
        // 树状结构根节点
        private final LogNode root;
        
        private LoggerContext() {
            this.root = new LogNode(null, this);
        }

final class LoggerWeakRef extends WeakReference<Logger> {
        /** 日志名称  **/
        private String                name;       
        /** 关联的LogNode **/
        private LogNode               node;       
        /** 父节点引用对象 **/
        private WeakReference<Logger> parentRef;  
        /** 是否处理 **/
        private boolean disposed = false;         

        LoggerWeakRef(Logger logger) {
            /** 将logger设置为弱引用 **/
            super(logger, loggerRefQueue);
            name = logger.getName();   cleanup
        }        

    /**
     * 添加logger
     */
    public boolean addLogger(Logger logger) {
        final String name = logger.getName();
        if (name == null) {
            throw new NullPointerException();
        }
        drainLoggerRefQueueBounded();
        /** 获取用户 LoggerContext **/
        LoggerContext cx = getUserContext();
        /** 将logger 添加到 LoggerContext**/
        if (cx.addLocalLogger(logger)) {
            /** 读取配置文件中"日志名称.handlers"属性值,并实例化为Handler关联到logger **/
            loadLoggerHandlers(logger, name, name + ".handlers");
            return true;
        } else {
            return false;
        }
    }

    boolean addLocalLogger(Logger logger) {
            return addLocalLogger(logger, requiresDefaultLoggers());
        }


    synchronized boolean addLocalLogger(Logger logger, boolean addDefaultLoggersIfNeeded) {

            if (addDefaultLoggersIfNeeded) {
                ensureAllDefaultLoggers(logger);
            }

            /** 获取日志名称 **/
            final String name = logger.getName();
            if (name == null) {
                throw new NullPointerException();
            }
            /**
             * 获取对应LoggerWeakRef,如果存在说明Logger已经添加直接返回
             * **/
            LoggerWeakRef ref = namedLoggers.get(name);
            if (ref != null) {
                if (ref.get() == null) {
                    ref.dispose();
                } else {
                    return false;
                }
            }

            /** 为添加Logger实例化LoggerWeakRef 添加到Context.namedLoggers属性中 **/
            final LogManager owner = getOwner();
            logger.setLogManager(owner);
            ref = owner.new LoggerWeakRef(logger);
            namedLoggers.put(name, ref);

            /** 读取配置文件中设置name + ".level" 日志等级 **/
            Level level = owner.getLevelProperty(name + ".level", null);
            if (level != null && !logger.isLevelInitialized()) {
                doSetLevel(logger, level);
            }

            /**
             * 读取配置文件中的 name + ".useParentHandlers" 设置当前Logger所对应父Logger是否忽略处理
             * 如果配置文件中的存在pname + ".level" 或 pname + ".handlers" 配置,则创建名称pname对应的Logger
             * **/
            processParentHandlers(logger, name);

            /** 获取并将当前logger存储到Context树状结构中 **/
            LogNode node = getNode(name);
            node.loggerRef = ref;
            Logger parent = null;
            /** 获取父节点 **/
            LogNode nodep = node.parent;
            while (nodep != null) {
                LoggerWeakRef nodeRef = nodep.loggerRef;
                if (nodeRef != null) {
                    parent = nodeRef.get();
                    if (parent != null) {
                        break;
                    }
                }
                nodep = nodep.parent;
            }

            /** 给当前添加logger建立父子关系 **/
            if (parent != null) {
                doSetParent(logger, parent);
            }
            node.walkAndSetParent(logger);
            ref.setNode(node);
            return true;
        }
2.4.2 从单例LogManager获取Logger
    Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
        /** 从LoggerContext中获取Logger **/
        Logger result = getLogger(name);
        /** 如果不存在则新建Logger添加到LoggerManger **/
        if (result == null) {
            Logger newLogger = new Logger(name, resourceBundleName, caller, this, false);
            do {
                if (addLogger(newLogger)) {
                    return newLogger;
                }
                result = getLogger(name);
            } while (result == null);
        }
        return result;
    }
    
    /**
     *
     * @param name  名称
     * @param resourceBundleName 资源
     * @param manager 关联的manager
     * @param isSystemLogger 是否是系统Logger
     */
    Logger(String name, String resourceBundleName, Class<?> caller, LogManager manager, boolean isSystemLogger) {
        this.manager = manager;
        this.isSystemLogger = isSystemLogger;
        setupResourceInfo(resourceBundleName, caller);
        this.name = name;
        levelValue = Level.INFO.intValue();
    }
3.2 打印日志
    /**
     * 记录INFO类型的消息
     */
    public void info(String msg) {
        log(Level.INFO, msg);
    }
    
    /**
     * 记录INFO类型的日志
     */
    public void log(Level level, String msg) {
        if (!isLoggable(level)) {
            return;
        }
        /** 将日志封装称LogRecord **/
        LogRecord lr = new LogRecord(level, msg);
        doLog(lr);
    }
    
    
   private void doLog(LogRecord lr) {
        /** 设置日志名称 **/
        lr.setLoggerName(name);
        /** 设置LogRecord相关属性**/
        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);
    }
    
    public void log(LogRecord record) {
        /** 校验匹配日志级别 **/
        if (!isLoggable(record.getLevel())) {
            return;
        }
        /**  使用Filter 过滤消息**/
        Filter theFilter = filter;
        if (theFilter != null && !theFilter.isLoggable(record)) {
            return;
        }

        Logger logger = this;
        while (logger != null) {
            /**
             * 获取日志处理器
             * 这里主要是针对RootLogger 获取配置文件中handlers属性加载设置到处理中
             * **/
            final Handler[] loggerHandlers = isSystemLogger
                ? logger.accessCheckedHandlers()
                : logger.getHandlers();

            /** 日志传递给关联handler 执行记录 **/
            for (Handler handler : loggerHandlers) {
                handler.publish(record);
            }

            /** 判断是否需要将日记记录传递给父logger处理 **/
            final boolean useParentHdls = isSystemLogger
                ? logger.useParentHandlers
                : logger.getUseParentHandlers();

            /** 无需交给父logger处理退出 **/
            if (!useParentHdls) {
                break;
            }
            /** 获取父looger记录日志 **/
            logger = isSystemLogger ? logger.parent : logger.getParent();
        }
    }

private final class RootLogger extends Logger {
        private RootLogger() {
            super("", null, null, LogManager.this, true);
        }

        @Override
        public void log(LogRecord record) {
            initializeGlobalHandlers();
            super.log(record);
        }

        @Override
        public void addHandler(Handler h) {
            initializeGlobalHandlers();
            super.addHandler(h);
        }

        @Override
        public void removeHandler(Handler h) {
            initializeGlobalHandlers();
            super.removeHandler(h);
        }

        @Override
        Handler[] accessCheckedHandlers() {
            initializeGlobalHandlers();
            return super.accessCheckedHandlers();
        }
    }


    /**
     * 读取配置文件handlers加载设置handlers
     */
    private synchronized void initializeGlobalHandlers() {
        if (initializedGlobalHandlers) {
            return;
        }
        initializedGlobalHandlers = true;
        if (deathImminent) {
            return;
        }
        loadLoggerHandlers(rootLogger, null, "handlers");
    }

参考

https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html

相关文章

网友评论

      本文标题:Java日志框架JUL

      本文链接:https://www.haomeiwen.com/subject/iyepsctx.html