`
qingkangxu
  • 浏览: 43612 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JDK Logging模块深入分析

阅读更多

日志输出是所有系统必备的,很多开发人员可能因为常常使用log4j而忽视了JDK logging模块,两者之间是否有联系?是怎样的联系?JDK logging处理细节是怎么样的?本周抛砖引玉,先分析JDK logging机制。

***从例子开始

JDK Logging的使用很简单,如下代码所示,先使用Logger类的静态方法getLogger就可以获取到一个logger,然后在任何地方都可以通过获取到的logger进行日志输入。比如类似logger.info("Main running.")的调用。

 


不做任何代码修改和JDK配置修改的话,运行上面的例子,你会发现,控制台只会出现【Main running.】这一句日志。如下问题应该呈现在你的大脑里…

 

1,【Main running.】以外的日志为什么没有输出?怎么让它们也能够出现?

2,日志中出现的时间、类名、方法名等是从哪里输出的?

3,为什么日志就会出现在控制台?

4,大型的系统可能有很多子模块(可简单理解为有很多包名),如何对这些子模块进行单独的日志级别控制?

5,扩充:apache那个流行的log4j项目和JDK的logging有联系吗,怎么实现自己的LoggerManager?

带着这些问题,可能你更有兴趣了解一下JDK的logging机制,本章为你分析这个简单模块的机制。

***术语解答:在深入分析之前,需要掌握以下术语

==>logger:对于logger,需要知道其下几个方面

1,代码需要输入日志的地方都会用到Logger,这几乎是一个JDK logging模块的代言人,我们常常用Logger.getLogger("com.aaa.bbb");获得一个logger,然后使用logger做日志的输出。

2,logger其实只是一个逻辑管理单元,其多数操作都只是作为一个中继者传递别的<角色>,比如说:Logger.getLogger(“xxx”)的调用将会依赖于LogManager类,使用logger输入日志信息的时候会调用logger中的所有handler进行日志的输入。

3,logger是有层次关系的,我们可一般性的理解为包名之间的父子继承关系。每个logger通常以java包名为其名称。子logger通常会从父logger继承logger级别、handler、ResourceBundle名(与国际化信息有关)等。

4,整个JVM会存在一个名称为空的root logger,所有匿名的logger都会把root logger作为其父

==>LogManager:整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于它,也包括配置文件的读取。LogManager中会有一个Hashtable【private Hashtable<String,WeakReference<Logger>> loggers】用于存储目前所有的logger,如果需要获取logger的时候,Hashtable已经有存在logger的话就直接返回Hashtable中的,如果hashtable中没有logger,则新建一个同时放入Hashtable进行保存。

==>Handler:用来控制日志输出的,比如JDK自带的ConsoleHanlder把输出流重定向到System.err输出,每次调用Logger的方法进行输出时都会调用Handler的publish方法,每个logger有多个handler。我们可以利用handler来把日志输入到不同的地方(比如文件系统或者是远程Socket连接).

==>Formatter:日志在真正输出前需要进行一定的格式话:比如是否输出时间?时间格式?是否输入线程名?是否使用国际化信息等都依赖于Formatter。

==>Log Level:不必说,这是做容易理解的一个,也是logging为什么能帮助我们适应从开发调试到部署上线等不同阶段对日志输出粒度的不同需求。JDK Log级别从高到低为OFF(231-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-231),每个级别分别对应一个数字,输出日志时级别的比较就依赖于数字大小的比较。但是需要注意的是:不仅是logger具有级别,handler也是有级别,也就是说如果某个logger级别是FINE,客户希望输入FINE级别的日志,如果此时logger对应的handler级别为INFO,那么FINE级别日志仍然是不能输出的。

总结对应关系:

==>LogManager与logger是1对多关系,整个JVM运行时只有一个LogManager,且所有的logger均在LogManager中

==>logger与handler是多对多关系,logger在进行日志输出的时候会调用所有的hanlder进行日志的处理

==>handler与formatter是一对一关系,一个handler有一个formatter进行日志的格式化处理

==>很明显:logger与level是一对一关系,hanlder与level也是一对一关系

***Logging配置:

JDK默认的logging配置文件为:$JAVA_HOME/jre/lib/logging.properties,可以使用系统属性java.util.logging.config.file指定相应的配置文件对默认的配置文件进行覆盖,配置文件中通常包含以下几部分定义:

1, handlers:用逗号分隔每个Handler,这些handler将会被加到root logger中。也就是说即使我们不给其他logger配置handler属性,在输出日志的时候logger会一直找到root logger,从而找到handler进行日志的输入。

2, .level是root logger的日志级别

3, <handler>.xxx是配置具体某个handler的属性,比如java.util.logging.ConsoleHandler.formatter便是为ConsoleHandler配置相应的日志Formatter.

4, logger的配置,所有以[.level]结尾的属性皆被认为是对某个logger的级别的定义,如com.bes.server.level=FINE是给名为[com.bes.server]的logger定义级别为FINE。顺便说下,前边提到过logger的继承关系,如果还有com.bes.server.webcontainer这个logger,且在配置文件中没有定义该logger的任何属性,那么其将会从[com.bes.server]这个logger进行属性继承。除了级别之外,还可以为logger定义handler和useParentHandlers(默认是为true)属性,如com.bes.server.handler=com.bes.test.ServerFileHandler(需要是一个extends java.util.logging.Handler的类),com.bes.server.useParentHandlers=false(意味着com.bes.server这个logger进行日志输出时,日志仅仅被处理一次,用自己的handler输出,不会传递到父logger的handler)。以下是JDK配置文件示例

 

***Logging执行原理

@@@Logger的获取

A,首先是调用Logger的如下方法获得一个logger

 

 

B,上面的调用会触发java.util.logging.LoggerManager的类初始化工作,LoggerManager有一个静态化初始化块(这是会先于LoggerManager的构造函数调用的~_~):

 

 

从静态初始化块中可以看出LoggerManager是可以使用系统属性java.util.logging.manager指定一个继承自java.util.logging.LoggerManager的类进行替换的,比如Tomcat启动脚本中就使用该机制以使用自己的LoggerManager。

不管是JDK默认的java.util.logging.LoggerManager还是自定义的LoggerManager,初始化工作中均会给LoggerManager添加两个logger,一个是名称为””的root logger,且logger级别设置为默认的INFO;另一个是名称为global的全局logger,级别仍然为INFO。

LogManager”类”初始化完成之后就会读取配置文件(默认为$JAVA_HOME/jre/lib/logging.properties),把配置文件的属性名<->属性值这样的键值对保存在内存中,方便之后初始化logger的时候使用。

C,A步骤中Logger类发起的getLogger操作将会调用java.util.logging.LoggerManager的如下方法:

 

 

可以看出,LoggerManager首先从现有的logger列表中查找,如果找不到的话,会新建一个looger并加入到列表中。当然很重要的是新建looger之后需要对logger进行初始化,这个初始化详见java.util.logging.LoggerManager#addLogger()方法中,改方法会根据配置文件设置logger的级别以及给logger添加handler等操作。

到此为止logger已经获取到了,你同时也需要知道此时你的logger中已经有级别、handler等重要信息,下面将分析输出日志时的逻辑。

@@@日志的输出

首先我们通常会调用Logger类下面的方法,传入日志级别以及日志内容。

 

 

该方法可以看出,Logger类首先是进行级别的校验,如果级别校验通过,则会新建一个LogRecord对象,LogRecord中除了日志级别,日志内容之外还会包含调用线程信息,日志时刻等;之后调用doLog(LogRecord lr)方法

 

 

doLog(LogRecord lr)方法中设置了ResourceBundle信息(这个与国际化有关)之后便直接调用log(LogRecord record) 方法

 

 

很清晰,while循环是重中之重,首先从logger中获取handler,然后分别调用handler的publish(LogRecordrecord)方法。while循环证明了前面提到的会一直把日志委托给父logger处理的说法,当然也证明了可以使用logger的useParentHandlers属性控制日志不进行往上层logger传递的说法。到此为止logger对日志的控制差不多算是完成,接下来的工作就是看handler的了,这里我们以java.util.logging.ConsoleHandler为例说明日志的输出。

 

 

ConsoleHandler构造函数中除了需要调用自身的configure()方法进行级别、filter、formatter等的设置之外,最重要的我们最关心的是setOutputStream(System.err)这一句,把系统错误流作为其输出。而ConsoleHandler的publish(LogRecordrecord)是继承自java.util.logging.StreamHandler的,如下所示:

 

 

方法逻辑也很清晰,首先是调用Formatter对消息进行格式化,说明一下:格式化其实是进行国际化处理的重要契机。然后直接把消息输出到对应的输出流中。需要注意的是handler也会用自己的level和LogRecord中的level进行比较,看是否真正输出日志。

总结

至此,整个日志输出过程已经分析完成。细心的读者应该可以解答如下四个问题了。

1,【Main running.】以外的日志为什么没有输出?怎么让它们也能够出现?

这就是JDK默认的logging.properties文件中配置的handler级别和跟级别均为info导致的,如果希望看到FINE级别日志,需要修改logging.properties文件,同时进行如下两个修改

java.util.logging.ConsoleHandler.level= FINE//修改

com.bes.logging.level=FINE//添加

2,日志中出现的时间、类名、方法名等是从哪里输出的?

请参照[java.util.logging.ConsoleHandler.formatter= java.util.logging.SimpleFormatter]配置中指定的java.util.logging.SimpleFormatter类,其publicsynchronized String format(LogRecord record) 方法说明了一切。

 

 

3,为什么日志就会出现在控制台?

看到java.util.logging.ConsoleHandler 类构造方法中的[setOutputStream(System.err)]语句,相信你已经明白。

4,大型的系统可能有很多子模块(可简单理解为有很多包名),如何对这些子模块进行单独的日志级别控制?

在logging.properties文件中分别对各个logger的级别进行定义,且最好使用java.util.logging.config.file属性指定自己的配置文件。

 

第5个问题暂时还解答不了,请继续期待,下一遍博文将讲述log4j和JDK logging的关系,以及怎么实现自己的LoggerManager以使得我们完全定制化logger、handler、formatter,掌控日志的国际化信息等。

 

分享到:
评论
2 楼 宇落YL 2016-04-23  
package com.bes.logging;

import java.util.logging.Level;
import java.util.logging.Logger;

public class LoggerTest {
      private static Loggerlogger = Logger.getLogger("com.bes.logging");
      public static void main(String argv[]) {
               // Log a FINEtracing message
               logger.info("Main running.");
               logger.fine("doingstuff");
               try {
                         Thread.currentThread().sleep(1000);// do some work
               } catch(Exception ex) {
                         logger.log(Level.WARNING,"trouble sneezing", ex);
               }
               logger.fine("done");
      }
}
1 楼 yysourcedownload 2015-01-27  
看完之后,有收获,但是还是有很多,大方面的问题。
Logger 日志的方式是有两种么?
1:jre下面的 那个 系统默认的
2:引入jar包log4j-1.2.14.jar

是这样的么?一直被这个问题困扰着!

相关推荐

    commons-logging-1.2源码(commons-logging-1.2-src.zip)

    通过分析`commons-logging-1.2-src`的源码,我们可以发现Commons Logging的设计目标是提供一个轻量级、灵活的日志解决方案,使得开发者可以在不修改代码的情况下切换日志框架。它的接口设计简洁,适配性强大,是Java...

    JDK 11 API中文帮助文档.rar

    通过阅读《JDK 11 API中文帮助文档》,开发者可以深入理解这些新特性和API的用法,从而更好地利用JDK 11提供的功能,编写出高效、可靠且易于维护的Java应用程序。无论是初学者还是经验丰富的开发者,都应该将其视为...

    jdk1.7.0_45-src:jdk1.7.0_45的源代码

    《深入探索JDK1.7.0_45源代码》 JDK1.7.0_45是Oracle公司发布的Java Development Kit的一个版本,它包含了Java编程语言的核心库和运行时环境。这个版本的源代码是理解Java平台工作原理、深入学习Java技术的宝贵资源...

    Java常用的日志框架对比和深入分析

    常见的日志框架有Log4j、Log4j2、Logback、JDK-Logging等,这些框架各有特点,适应不同的应用场景。 **Log4j** 是Apache组织开发的一个功能强大的日志库,它允许将日志信息输出到控制台、文件、GUI或操作系统事件...

    java11英文api文档,jdk-11.0.5_doc-all.zip

    此外,还有其他核心模块,如`java.logging`、`java.sql`和`java.xml`,分别用于日志记录、数据库访问和XML处理。 2. **集合框架**:Java 11的API文档详细介绍了`java.util`包下的集合框架,如List、Set、Map接口...

    java_jdk1.8.0_111:java_jdk1.8.0_111源码(Java,非C)

    通过深入研究Java JDK 1.8.0_111的源码,开发者不仅可以提高对Java语言的理解,还能洞察Java平台的底层运作机制,从而提升编程技能和解决问题的能力。这个开源项目为学习和研究提供了宝贵的资源。

    CXF创建Webservice接口与Spring整合实例

    通过分析这些文件,你可以更深入地理解上述步骤的实际应用。 总之,通过CXF创建带有用户认证的Web服务接口,并与SSH框架整合,可以构建一个安全、可扩展的分布式系统。这种集成方案使得开发者能够充分利用SSH的便利...

    03 mybatis高级(1)3

    Mybatis的日志模块具有灵活性,它并不直接实现日志功能,而是提供了一套标准的日志接口,允许用户根据需要接入各种第三方日志框架,如SLF4J、Commons Logging、Log4j2、Log4j和JDK自带的日志。Mybatis会自动检测这些...

    JBoss启动与运行过程的研究

    第2章至第5章将深入探讨JBoss的启动步骤、启动过程中调用的配置文件、运行过程的分析以及输出信息的含义。通过理解这些内容,开发者和运维人员能更好地了解JBoss的工作原理,从而有效地调试、优化和管理服务器。

    java.lang.NoClassDefFoundError:

    2. **模块依赖缺失**:项目依赖于某个外部库,但该库未被正确安装或配置。 3. **版本冲突**:存在不同版本的依赖库,导致某些类找不到正确的版本。 4. **构建工具配置错误**:如Maven、Gradle等构建工具的配置出现...

    Mybatis源码分析-上课笔记1

    本篇文章将深入分析MyBatis的核心概念和源码结构。 首先,我们来看一下MyBatis的主要组件: 1. **Configuration**:这是MyBatis的核心配置对象,它包含了全局配置信息,如数据源、事务管理器、Mappers等。...

    jodd 完整包有测试例子

    - **Logging**:集成了多种日志框架,如 Log4j 和 JDK 内置的日志,提供统一的日志接口。 - **Miscellaneous**:包括各种实用工具类,如字符串操作、文件系统操作、加密解密等。 在压缩包中,`etc` 文件夹可能...

    spring源码+spring所需jar包

    - **AOP代理**:Spring使用动态代理实现AOP,可以创建JDK代理或CGLIB代理,实现对目标对象的拦截。 三、Spring所需jar包 Spring框架的运行依赖于一系列的jar包,包括Spring自身的核心库和其他第三方库。这些jar包...

    03 mybatis高级(1)2

    1. **适配器模式**:Mybatis允许用户集成不同的日志框架,如SLF4J、Commons Logging、Log4J2、Log4J以及JDK自带的日志API。适配器模式在此起到关键作用,它将各种日志框架的接口转换为Mybatis期望的日志级别(trace...

    openjdk-12-windows-x64-bin

    6. **JDK源代码的模块化 (JEP 320)**:继续推进Java模块化的工作,进一步优化了源代码的组织和构建过程。 在安装和使用OpenJDK 12时,用户通常会解压"jdk-12"这个文件夹,其中包含以下关键文件和目录: - `bin`:...

    kettle-8.2.0.0源码

    源码分析: 1. **源码结构**:源码包含了Kettle的所有组件,如Spoon(图形化设计工具)、Kitchen(命令行执行工具)、Pan(批处理执行引擎)等。源码分为多个模块,每个模块负责不同的功能,如Engine(核心执行引擎...

    Java学习之Reference、Eclipse调优、jvisualvm使用

    通过阅读开源框架或JDK的源码,我们可以深入理解其设计原理和实现细节,这对于问题排查、性能优化和自定义扩展都大有裨益。 至于"工具",在Java开发中,除了Eclipse和jvisualvm,还有许多其他实用工具,如IntelliJ ...

    Spring的AOP实例(XML+@AspectJ双版本解析+源码+类库)

    本篇文章将深入探讨Spring的AOP(面向切面编程)特性,包括XML配置和@AspectJ注解方式的实践,同时结合源码分析,帮助开发者更全面地理解和应用这一重要概念。 **AOP概述** 面向切面编程(Aspect-Oriented ...

    tomcat源码依赖包

    3. **Apache Commons**:Apache Commons是Apache软件基金会的一个项目,提供了大量的Java实用工具类,Tomcat源码中可能使用了如Commons Logging、Commons Lang、Commons IO等模块。 4. **Servlet API**:Tomcat作为...

Global site tag (gtag.js) - Google Analytics