欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java底層JDK?Logging日志模塊處理細節(jié)深入分析

 更新時間:2022年03月24日 14:39:47   作者:qingkangxu  
這篇文章主要為大家介紹了java底層JDK?Logging日志模塊處理細節(jié)深入分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

日志輸出是所有系統(tǒng)必備的,很多開發(fā)人員可能因為常常使用log4j而忽視了JDK logging模塊,兩者之間是否有聯(lián)系?是怎樣的聯(lián)系?JDK logging處理細節(jié)是怎么樣的?本周拋磚引玉,先分析JDK logging機制。 

從例子開始

JDK Logging的使用很簡單,如下代碼所示,先使用Logger類的靜態(tài)方法getLogger就可以獲取到一個logger,然后在任何地方都可以通過獲取到的logger進行日志輸入。

比如類似logger.info("Main running.")的調(diào)用。

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配置修改的話,運行上面的例子,你會發(fā)現(xiàn),控制臺只會出現(xiàn)【Main running.】這一句日志。如下問題應該呈現(xiàn)在你的大腦里…

1,【Main running.】以外的日志為什么沒有輸出?怎么讓它們也能夠出現(xiàn)?

2,日志中出現(xiàn)的時間、類名、方法名等是從哪里輸出的?

3,為什么日志就會出現(xiàn)在控制臺?

4,大型的系統(tǒng)可能有很多子模塊(可簡單理解為有很多包名),如何對這些子模塊進行單獨的日志級別控制?

5,擴充:apache那個流行的log4j項目和JDK的logging有聯(lián)系嗎,怎么實現(xiàn)自己的LoggerManager?

帶著這些問題,可能你更有興趣了解一下JDK的logging機制,本章為你分析這個簡單模塊的機制。

術語解答

在深入分析之前,需要掌握以下術語

logger: 對于logger,需要知道其下幾個方面

1,代碼需要輸入日志的地方都會用到Logger,這幾乎是一個JDK logging模塊的代言人,我們常常用Logger.getLogger("com.aaa.bbb");獲得一個logger,然后使用logger做日志的輸出。

2,logger其實只是一個邏輯管理單元,其多數(shù)操作都只是作為一個中繼者傳遞別的<角色>,比如說:Logger.getLogger(“xxx”)的調(diào)用將會依賴于LogManager類,使用logger輸入日志信息的時候會調(diào)用logger中的所有handler進行日志的輸入。

3,logger是有層次關系的,我們可一般性的理解為包名之間的父子繼承關系。每個logger通常以java包名為其名稱。子logger通常會從父logger繼承l(wèi)ogger級別、handler、ResourceBundle名(與國際化信息有關)等。

4,整個JVM會存在一個名稱為空的root logger,所有匿名的logger都會把root logger作為其父

LogManager:整個JVM內(nèi)部所有l(wèi)ogger的管理,logger的生成、獲取等操作都依賴于它,也包括配置文件的讀取。LogManager中會有一個Hashtable

private Hashtable<String,WeakReference<Logger>> loggers

用于存儲目前所有的logger,如果需要獲取logger的時候,Hashtable已經(jīng)有存在logger的話就直接返回Hashtable中的,如果hashtable中沒有l(wèi)ogger,則新建一個同時放入Hashtable進行保存。 

Handler:用來控制日志輸出的,比如JDK自帶的ConsoleHanlder把輸出流重定向到System.err輸出,每次調(diào)用Logger的方法進行輸出時都會調(diào)用Handler的publish方法,每個logger有多個handler。我們可以利用handler來把日志輸入到不同的地方(比如文件系統(tǒng)或者是遠程Socket連接). 

Formatter:日志在真正輸出前需要進行一定的格式話:比如是否輸出時間?時間格式?是否輸入線程名?是否使用國際化信息等都依賴于Formatter。 

Log Level:不必說,這是做容易理解的一個,也是logging為什么能幫助我們適應從開發(fā)調(diào)試到部署上線等不同階段對日志輸出粒度的不同需求。

JDK Log級別從高到低為

OFF(231-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-231)

每個級別分別對應一個數(shù)字,輸出日志時級別的比較就依賴于數(shù)字大小的比較。但是需要注意的是:不僅是logger具有級別,handler也是有級別,也就是說如果某個logger級別是FINE,客戶希望輸入FINE級別的日志,如果此時logger對應的handler級別為INFO,那么FINE級別日志仍然是不能輸出的。

總結對應關系

LogManager與logger是1對多關系,整個JVM運行時只有一個LogManager,且所有的logger均在LogManager中

logger與handler是多對多關系,logger在進行日志輸出的時候會調(diào)用所有的hanlder進行日志的處理

handler與formatter是一對一關系,一個handler有一個formatter進行日志的格式化處理

很明顯:logger與level是一對一關系,hanlder與level也是一對一關系 

Logging配置:

JDK默認的logging配置文件為:$JAVA_HOME/jre/lib/logging.properties,可以使用系統(tǒng)屬性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配置文件示例

handlers= java.util.logging.FileHandler,java.util.logging.ConsoleHandler
.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
sun.rmi.transport.tcp.logLevel = FINE 

Logging執(zhí)行原理

Logger的獲取

A,首先是調(diào)用Logger的如下方法獲得一個logger

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

B,上面的調(diào)用會觸發(fā)java.util.logging.LoggerManager的類初始化工作,LoggerManager有一個靜態(tài)化初始化塊(這是會先于LoggerManager的構造函數(shù)調(diào)用的):

static {
   AccessController.doPrivileged(newPrivilegedAction<Object>() {
       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;
       }
   });
}

從靜態(tài)初始化塊中可以看出LoggerManager是可以使用系統(tǒng)屬性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),把配置文件的屬性名<->屬性值這樣的鍵值對保存在內(nèi)存中,方便之后初始化logger的時候使用。

C,A步驟中Logger類發(fā)起的getLogger操作將會調(diào)用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首先從現(xiàn)有的logger列表中查找,如果找不到的話,會新建一個looger并加入到列表中。當然很重要的是新建looger之后需要對logger進行初始化,這個初始化詳見java.util.logging.LoggerManager#addLogger()方法中,改方法會根據(jù)配置文件設置logger的級別以及給logger添加handler等操作。

 到此為止logger已經(jīng)獲取到了,你同時也需要知道此時你的logger中已經(jīng)有級別、handler等重要信息,下面將分析輸出日志時的邏輯。 

日志的輸出

首先我們通常會調(diào)用Logger類下面的方法,傳入日志級別以及日志內(nèi)容。

    public void log(Levellevel, String msg) {
          if (level.intValue() < levelValue ||levelValue == offValue) {
              return;
          }
          LogRecord lr = new LogRecord(level, msg);
          doLog(lr);
    }

該方法可以看出,Logger類首先是進行級別的校驗,如果級別校驗通過,則會新建一個LogRecord對象,LogRecord中除了日志級別,日志內(nèi)容之外還會包含調(diào)用線程信息,日志時刻等;之后調(diào)用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信息(這個與國際化有關)之后便直接調(diào)用log(LogRecord record) 方法 

   public void log(LogRecord record) {
          if (record.getLevel().intValue() <levelValue || levelValue == offValue) {
              return;
          }
          synchronized (this) {
              if (filter != null &&!filter.isLoggable(record)) {
                  return;
              }
          }
          Logger logger = this;
          while (logger != null) {
              Handler targets[] = logger.getHandlers();
              if(targets != null) {
                  for (int i = 0; i < targets.length; i++){
                           targets[i].publish(record);
                       }
              }
              if(!logger.getUseParentHandlers()) {
                       break;
              }
              logger= logger.getParent();
          }
    }

很清晰,while循環(huán)是重中之重,首先從logger中獲取handler,然后分別調(diào)用handler的publish(LogRecordrecord)方法。while循環(huán)證明了前面提到的會一直把日志委托給父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構造函數(shù)中除了需要調(diào)用自身的configure()方法進行級別、filter、formatter等的設置之外,最重要的我們最關心的是setOutputStream(System.err)這一句,把系統(tǒng)錯誤流作為其輸出。而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);
       }
    }

方法邏輯也很清晰,首先是調(diào)用Formatter對消息進行格式化,說明一下:格式化其實是進行國際化處理的重要契機。然后直接把消息輸出到對應的輸出流中。需要注意的是handler也會用自己的level和LogRecord中的level進行比較,看是否真正輸出日志。

 總結

至此,整個日志輸出過程已經(jīng)分析完成。細心的讀者應該可以解答如下四個問題了。

1,【Main running.】以外的日志為什么沒有輸出?怎么讓它們也能夠出現(xiàn)?

這就是JDK默認的logging.properties文件中配置的handler級別和跟級別均為info導致的,如果希望看到FINE級別日志,需要修改logging.properties文件,同時進行如下兩個修改

??? java.util.logging.ConsoleHandler.level= FINE//修改
??? com.bes.logging.level=FINE//添加

2,日志中出現(xiàn)的時間、類名、方法名等是從哪里輸出的?

請參照

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

3,為什么日志就會出現(xiàn)在控制臺?

看到java.util.logging.ConsoleHandler 類構造方法中的[setOutputStream(System.err)]語句,相信你已經(jīng)明白。

4,大型的系統(tǒng)可能有很多子模塊(可簡單理解為有很多包名),如何對這些子模塊進行單獨的日志級別控制?

在logging.properties文件中分別對各個logger的級別進行定義,且最好使用java.util.logging.config.file屬性指定自己的配置文件。

第5個問題暫時還解答不了,請繼續(xù)期待,下一遍博文將講述log4j和JDK logging的關系,以及怎么實現(xiàn)自己的LoggerManager以使得我們完全定制化logger、handler、formatter,掌控日志的國際化信息等,更多關于java JDK Logging日志模塊處理細節(jié)的資料請關注腳本之家其它相關文章!

相關文章

  • java中對象和JSON格式的轉換方法代碼

    java中對象和JSON格式的轉換方法代碼

    JSON格式可以輕松地以面向對象的方式轉換為Java對象,下面這篇文章主要給大家介紹了關于java中對象和JSON格式的轉換方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-12-12
  • Springboot常用注解及配置文件加載順序詳解

    Springboot常用注解及配置文件加載順序詳解

    這篇文章主要介紹了Springboot常用注解及配置文件加載順序,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-11-11
  • Spring Boot 自動配置的實現(xiàn)

    Spring Boot 自動配置的實現(xiàn)

    這篇文章主要介紹了Spring Boot 自動配置的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • 深入理解Java嵌套類和內(nèi)部類

    深入理解Java嵌套類和內(nèi)部類

    本篇文章主要介紹了深入理解Java嵌套類和內(nèi)部類,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • SpringBoot自動裝配原理詳細解析

    SpringBoot自動裝配原理詳細解析

    這篇文章主要介紹了SpringBoot自動裝配原理詳細解析,一個對象交給Spring來管理的三種方式 @Bean @Compoment @Import,
    @Bean主要在@Configuration中,通過方法進行注入相關的Bean,@Compoent與@Service歸為一類,在類上加注入對應的類,需要的朋友可以參考下
    2024-01-01
  • java彩色瓷磚編程題分析

    java彩色瓷磚編程題分析

    這篇文章主要介紹了java彩色瓷磚編程題的詳細解題思路以及解決方法分享,對此有興趣的參考下。
    2018-02-02
  • SpringBoot屬性注入的兩種方法

    SpringBoot屬性注入的兩種方法

    這篇文章主要介紹了SpringBoot屬性注入的兩種方法,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2020-11-11
  • 帶你用Java方法輕松實現(xiàn)樹的同構

    帶你用Java方法輕松實現(xiàn)樹的同構

    給定兩棵樹T1和T2。如果T1可以通過若干次左右孩子互換就變成T2,則我們稱兩棵樹是“同構”的。例如圖1給出的兩棵樹就是同構的,因為我們把其中一棵樹的結點A、B、G的左右孩子互換后,就得到另外一棵樹
    2021-06-06
  • Mybatis 中Mapper使用package方式配置報錯的解決方案

    Mybatis 中Mapper使用package方式配置報錯的解決方案

    這篇文章主要介紹了Mybatis 中Mapper使用package方式配置報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • java數(shù)學歸納法非遞歸求斐波那契數(shù)列的方法

    java數(shù)學歸納法非遞歸求斐波那契數(shù)列的方法

    這篇文章主要介紹了java數(shù)學歸納法非遞歸求斐波那契數(shù)列的方法,涉及java非遞歸算法的使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07

最新評論