java底层JDK Logging日志模块怎么处理

从例子开始

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

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");      }}

登录后复制

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

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

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

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

立即学习“Java免费学习笔记(深入)”;

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> 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,  .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配置文件示例

handlers= java.util.logging.FileHandler,java.util.logging.ConsoleHandler.level= INFOjava.util.logging.FileHandler.pattern = %h/java%u.logjava.util.logging.FileHandler.limit = 50000java.util.logging.FileHandler.count = 1java.util.logging.FileHandler.formatter =java.util.logging.XMLFormatterjava.util.logging.ConsoleHandler.level = INFOjava.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormattercom.xyz.foo.level = SEVEREsun.rmi.transport.tcp.logLevel = FINE

登录后复制

Logging执行原理

Logger的获取

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

    public static synchronized Logger getLogger(String name) {           LogManager manager =LogManager.getLogManager();        returnmanager.demandLogger(name);    }

登录后复制

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

static {   AccessController.doPrivileged(newPrivilegedAction() {       public Object run() {           String cname =null;           try {               cname =System.getProperty("java.util.logging.manager");               if (cname !=null) {                  try {                       Class clz =ClassLoader.getSystemClassLoader().loadClass(cname);                       manager= (LogManager) clz.newInstance();                   } catch(ClassNotFoundException ex) {               Class clz =Thread.currentThread().getContextClassLoader().loadClass(cname);                      manager= (LogManager) clz.newInstance();                   }               }           } catch (Exceptionex) {              System.err.println("Could not load Logmanager "" + cname+ """);              ex.printStackTrace();           }           if (manager ==null) {               manager = newLogManager();           }                 manager.rootLogger= manager.new RootLogger();          manager.addLogger(manager.rootLogger);            Logger.global.setLogManager(manager);          manager.addLogger(Logger.global);            return null;       }   });}

登录后复制

从静态初始化块中可以看出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的如下方法:

     Logger demandLogger(String name) {       Logger result =getLogger(name);       if (result == null) {           result = newLogger(name, null);           addLogger(result);           result =getLogger(name);       }       return result;     }

登录后复制

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

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

日志的输出

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

    public void log(Levellevel, String msg) {          if (level.intValue() 

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

    private void doLog(LogRecord lr) {          lr.setLoggerName(name);          String ebname =getEffectiveResourceBundleName();          if (ebname != null) {              lr.setResourceBundleName(ebname);              lr.setResourceBundle(findResourceBundle(ebname));          }          log(lr);    }

登录后复制

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

   public void log(LogRecord record) {          if (record.getLevel().intValue() 

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

public class ConsoleHandler extends StreamHandler {    public ConsoleHandler() {          sealed = false;          configure();          setOutputStream(System.err);          sealed = true;    }

登录后复制

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

 public synchronized void publish(LogRecord record) {       if(!isLoggable(record)) {           return;       }       String msg;       try {           msg =getFormatter().format(record);       } catch (Exception ex){           // We don't want tothrow an exception here, but we           // report theexception 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 tothrow an exception here, but we           // report theexception to any registered ErrorManager.           reportError(null,ex, ErrorManager.WRITE_FAILURE);       }    }

登录后复制

方法逻辑也很清晰,首先是调用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) 方法说明了一切。

public synchronized String format(LogRecord record) { StringBuffer sb = new StringBuffer(); // Minimize memory allocations here. dat.setTime(record.getMillis()); args[0] = dat; StringBuffer text = new StringBuffer(); if (formatter == null) { formatter = new MessageFormat(format); } formatter.format(args, text, null); sb.append(text); sb.append(" "); if (record.getSourceClassName() != null) { sb.append(record.getSourceClassName()); } else { sb.append(record.getLoggerName()); } if (record.getSourceMethodName() != null) { sb.append(" "); sb.append(record.getSourceMethodName()); } sb.append(lineSeparator); String message = formatMessage(record); sb.append(record.getLevel().getLocalizedName()); sb.append(": "); sb.append(message); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = newStringWriter(); PrintWriter pw = newPrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString();}public synchronized String format(LogRecord record) {    StringBuffer sb = new StringBuffer();    // Minimize memory allocations here.    dat.setTime(record.getMillis());    args[0] = dat;    StringBuffer text = new StringBuffer();    if (formatter == null) {        formatter = new MessageFormat(format);    }    formatter.format(args, text, null);    sb.append(text);    sb.append(" ");    if (record.getSourceClassName() != null) {             sb.append(record.getSourceClassName());    } else {        sb.append(record.getLoggerName());    }    if (record.getSourceMethodName() != null) {        sb.append(" ");       sb.append(record.getSourceMethodName());    }    sb.append(lineSeparator);    String message = formatMessage(record);   sb.append(record.getLevel().getLocalizedName());    sb.append(": ");    sb.append(message);    sb.append(lineSeparator);    if (record.getThrown() != null) {        try {            StringWriter sw = newStringWriter();            PrintWriter pw = newPrintWriter(sw);           record.getThrown().printStackTrace(pw);            pw.close();             sb.append(sw.toString());        } catch (Exception ex) {        }    }    return sb.toString();}

登录后复制

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

立即学习“Java免费学习笔记(深入)”;

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

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

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

以上就是java底层JDK Logging日志模块怎么处理的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2603583.html

(0)
上一篇 2025年3月6日 19:42:18
下一篇 2025年2月26日 18:52:09

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • Java项目怎么部署到云服务器中

    一、购买云服务器和安装系统 下单的时候一般会选择安装哪种操作系统,我一般用的是 centos,6.x 和 7.x 都行。 二、安装宝塔面板 什么服务器都一样,只是不同的操作系统可能命令不同。 1、ssh 连接到服务器 ssh 的账号和密码一…

    2025年3月6日 编程技术
    200
  • java服务器容器调优的方法是什么

    1.为什么要进行项目性能调优 在项目进行发布之前,需要对项目进行压力测试,可以检测出项目的性能问题,比如说:项目响应时间较慢,项目每次能解决的请求数较少,项目的瓶颈,项目查询数据时间较慢等问题,检测出来之后,就需要调优,意思就是说你的项目接…

    编程技术 2025年3月6日
    200
  • Docker部署Java web系统的方法

    docker部署java web系统  1.在root目录下创建一个路径test/app mkdir test && cd test&& mkdir app &&cd app 2.将apach…

    2025年3月6日
    200
  • 基于Java怎么实现Redis多级缓存

    一、多级缓存 1. 传统缓存方案 请求到达tomcat后,先去redis中获取缓存,不命中则去mysql中获取 2. 多级缓存方案 tomcat的请求并发数,是远小于redis的,因此tomcat会成为瓶颈 利用请求处理每个环节,分别添加缓…

    2025年3月6日 编程技术
    200
  • java获取到heapdump文件后怎么快速分析

    heapdump文件介绍 heap dump: heap dump文件是一个二进制文件,它保存了某一时刻jvm堆中对象使用情况。heapdump文件是指定时刻的java堆栈的快照,是一种镜像文件。 产生heap dump(内存溢出)错误原因…

    2025年3月6日 编程技术
    200
  • Java怎么实现登录与注册页面

    用java实现的登录与注册页面,实现了客户端(浏览器)到服务器(tomcat)再到后端(servlet程序)数据的交互。这里在注册页面加入了验证码验证。 注册的html代码 登录后复制登录后复制    注册页面    function ch…

    2025年3月6日
    200
  • Java如何实现鲜花商城系统

    项目介绍 该项目为前后台项目,分为普通用户与管理员两种角色,前台普通用户登录,后台管理员登录; 管理员角色包含以下功能: 管理员登录,用户管理,鲜花类别管理,鲜花管理,订单管理并发货,留言管理,系统公告管理等功能。 用户角色包含以下功能: …

    2025年3月6日 编程技术
    200
  • Java中的SPI机制是什么

    1: SPI机制简介 spi 全称是 service provider interface,是一种 jdk 内置的动态加载实现扩展点的机制,通过 spi 技术我们可以动态获取接口的实现类,不用自己来创建。这个不是什么特别的技术,只是 一种设…

    2025年3月6日
    200
  • Java语言中的医疗健康应用开发介绍

    随着医疗科技的发展和互联网的普及,医疗健康应用越来越受到人们的关注。使用java语言来开发医疗健康应用不仅可以提高开发效率,还可以保证软件的稳定性和安全性。本文将介绍java语言在医疗健康应用开发中的应用。 一、Java语言的优点 Java…

    编程技术 2025年3月6日
    200
  • Java语言应用服务器的搭建方法

    随着互联网的普及和应用开发的需要,java语言应用服务器也逐渐成为了重要的支撑平台之一。它不仅可以提供运行环境,还可以支持多种程序开发语言,如java、php和python等。本文将介绍java语言应用服务器的搭建方法,帮助广大读者快速搭建…

    编程技术 2025年3月6日
    200

发表回复

登录后才能评论