隨筆-128  評論-55  文章-5  trackbacks-0

          引言
          作為一名Java 程序員,最熟悉的、使用最多的調(diào)用恐怕莫過于System.out.print(“…”)。當(dāng)你沒有調(diào)試工具而要跟蹤一個(gè)變量的值得時(shí)候;當(dāng)你需要顯示捕獲的Exception、Error的時(shí)候;當(dāng)你想知道程序在運(yùn)行的時(shí)候究竟發(fā)生了什么的時(shí)候,通常的做法就是調(diào)用System.out.print把他們在終端、控制臺上打印出來。這種方式對于輸出信息的分類、格式化及永久保存帶來諸多不便。雖然我們可以把它寫入一個(gè)文件然后進(jìn)行分析,但是這要需要編寫額外的程序代碼,其成本不可忽視!而由此給目標(biāo)系統(tǒng)本身增加的復(fù)雜程度不可避免的使開發(fā)、調(diào)試陷入一個(gè)深深的迷潭。

          JDK1.4的推出,使得這一切即將成為歷史。讓我們向System.out.print()告別吧,使用Java Logging API為自己的程序構(gòu)建一個(gè)完整的日志記錄系統(tǒng)!

           

          一、第一個(gè)實(shí)例

              先看一個(gè)簡單的實(shí)例:SimpleLoggingTest.java

          1  import java.util.logging.*;

          2 public class SimpleLoggingTest {

          3      public static void main(String args[]) {

          4           //程序的其它處理

          5           //使用Logger的靜態(tài)方法獲得一個(gè)匿名          //Logger

          6           Logger logger1 = Logger.getAnonymousLogger();

          7           //記錄消息

          8           logger1.log(Level.INFO,"第一條日志記錄");

          9           //程序的其它處理

          10      }

          11 }

          注意:編譯、執(zhí)行該程序需要JDK1.4及以上版本的支持。


          運(yùn)行該程序,可以在控制臺看到程序運(yùn)行結(jié)果:


          2003-1-14 15:09:40 SimpleLoggingTest main

          信息: 第一條日志記錄

           

          首先,程序引用java.util.Logging包(第1行)。接著,在適當(dāng)?shù)臅r(shí)候獲得一個(gè)Logger(記錄器)類的實(shí)例(第6行,獲取一個(gè)匿名的Logger)。最后,在程序需要記錄信息的地方調(diào)用Logger類的log方法進(jìn)行記錄(第8行,記錄一個(gè)INFO級別的消息)。

           

          二、Java Logging API

               Java Logging API封裝在JDK1.4.0的java.util.Logging包中。它通過產(chǎn)生便于最終用戶、系統(tǒng)管理員、故障維護(hù)工程師以及軟件開發(fā)團(tuán)隊(duì)(工程師)進(jìn)行分析的日志記錄為軟件的開發(fā)調(diào)試和維護(hù)提供便利的手段。它捕獲操作系統(tǒng)平臺和執(zhí)行程序的安全故障、配置錯(cuò)誤、執(zhí)行瓶頸和(或)Bug等數(shù)據(jù)信息,以純文本、XML或程序員自定的某種方式將其格式化成日志記錄,然后傳遞給內(nèi)存、系統(tǒng)輸出流、控制臺、文件、Sockets等多種系統(tǒng)資源進(jìn)行緩存和輸出。

           

          (一)該軟件包中的關(guān)鍵類:

             Logger:  應(yīng)用程序進(jìn)行日志記錄調(diào)用的主要實(shí)體。 Logger對象用于記錄特定系統(tǒng)或應(yīng)用程序的消息。

             LogRecord:  用于在日志框架和單個(gè)記錄處理程序之間傳遞記錄請求。

             Handler:  日志數(shù)據(jù)的最終輸出處理器。它將LogRecord對象導(dǎo)出到各種目標(biāo),包括內(nèi)存、輸出流、控制臺、文件和套接字。多種 Handler子類可供用于這種用途。

             Level:  定義一組標(biāo)準(zhǔn)的記錄級別,可用于控制記錄的輸出。可以把程序配置為只輸出某些級別的記錄,而忽略其他級別的輸出。

             Filter:  精細(xì)過濾、控制記錄的內(nèi)容,比記錄級別所提供的控制準(zhǔn)確得多。記錄API支持通用的過濾器機(jī)制,這種機(jī)制允許應(yīng)用程序代碼添加任意過濾器以便控制記錄的輸出。

             Formatter:  為LogRecord對象的格式化提供支持。

             LogManager: Java Logging框架中唯一的、全局的對象,用于維護(hù)與Logger記錄器及日志服務(wù)的一系列共享的數(shù)據(jù)結(jié)構(gòu)及狀態(tài)。它負(fù)責(zé)整個(gè)日志框架的初始化、維護(hù)一組全局性的Handle對象、維護(hù)一個(gè)樹形結(jié)構(gòu)的Logger的名字空間、診測日志框架配置文件的改變從而重新讀入并應(yīng)用相關(guān)的參數(shù)以及負(fù)責(zé)程序停止運(yùn)行時(shí)整個(gè)日志框架的清理工作。

           

          (二)Logger

          1、Logger的命名空間

          在SimpleLoggingTest.java實(shí)例中,我們使用了一個(gè)匿名的(沒有命名的)Logger對象。在Java Logging 框架中,Logger是可以命名的。Logger的名字空間與java類的名字空間相同的結(jié)構(gòu)相同:使用“.”間隔的字符串。Logger的名字空間體現(xiàn)了Logger的層次結(jié)構(gòu)。例如:命名為“a.b”的Logger是命名為“a.b.c”的“父”(上一級)Logger記錄器。Logger的命名可以是任意的字符串,一般情況下,使用包或類的名字為Logger 進(jìn)行命名。

          Logger的名字空間由全局單列類LogManager的實(shí)例進(jìn)行創(chuàng)建、維護(hù)。

          匿名Logger不被存儲在命名空間中。

          2、創(chuàng)建Logger實(shí)例

          Logger對象可以通過調(diào)用工廠方法getLogger或getAnonymousLogger獲取。


          //獲取一個(gè)名為“A”的Logger對象

          Logger loggerA= Logger.getLogger(“A”);

          // 獲取一個(gè)名為“A.B”的Logger對象,其上級記錄器為loggerA。

          Logger loggerAB= Logger.getLogger(“A.B”);

          //獲取一個(gè)匿名Logger對象

          Logger loggerTmp = Logger.getAnonymousLogger();

           

          對非匿名Logger,getLogger先在命名空間中查找同名的Logger對象,如果有,則返回該Logger對象;如果不存在,則在命名空間中創(chuàng)建注冊一個(gè)新的Logger對象,并與其上級Logger對象相關(guān)聯(lián)。

          匿名Logger對象屬于創(chuàng)建它的對象的私有對象,只能由創(chuàng)建它的對象使用,記錄一些臨時(shí)性的日志信息。而命名Logger對象使全局性的,在日志框架的生存期內(nèi),除了創(chuàng)建它的對象外還,可由其它對象用于記錄日志信息。

          匿名的Logger對象由一個(gè)全局的的root Logger “” 對象(root Logger的名字為空)。這意味著所有匿名Logger對象將從root Logger “”中繼承行為。

          匿名Logger對象通常用于java Applet應(yīng)用中。它去掉了在運(yùn)行過程中的一班性的安全檢查,允許其創(chuàng)建類對象對Logger的控制、狀態(tài)信息進(jìn)行修改,如:setLevel設(shè)置Logger的日志消息記錄級別;addHandle增加Logger的Handle(處理器)對象等。

          一個(gè)Logger對象可以擁有有零個(gè)到多個(gè)Handler實(shí)例。當(dāng)沒有Handler時(shí),如不禁止日志記錄沿名字空間向上傳遞,那該Logger對象的日志消息記錄將有其擁有Handler實(shí)例的上級Logger進(jìn)行處理。當(dāng)一個(gè)Logger對象擁有多個(gè)Handler實(shí)例對象時(shí),其記錄的日志數(shù)據(jù)將被所有的Handler逐一進(jìn)行處理。


          (三)Handler
           Handler對象接收傳來的日志消息將其輸出。Handler可以把日志消息輸出到多種目標(biāo)資源,如:輸出到控制臺進(jìn)行顯示、寫入日志文件、傳送到網(wǎng)絡(luò)上的遠(yuǎn)程日志服務(wù)進(jìn)行處理、寫入系統(tǒng)日志等任何物理資源。
          Handler對象在創(chuàng)建時(shí)使用LogManager對象的相關(guān)屬性的默認(rèn)值(如Handler的Filter、Formatter、Level等對象屬性)進(jìn)行初始化。
          Handler對象可通過調(diào)用setLevel(Level.OFF)暫停工作;通過調(diào)用setLevel設(shè)置適當(dāng)?shù)挠涗浫罩鞠⒓墑e恢復(fù)工作。
          Handler是一個(gè)抽象類。在J2SDK1.4中,其子類及它們之間的關(guān)系見圖一。

          1、   MemoryHandler Handler的子類,在內(nèi)存中的一個(gè)循環(huán)緩沖區(qū)用于緩存日志記錄請求。通常MemoryHandler只簡單的把傳入的LogRecords存儲到它的內(nèi)存中。這種緩存的開銷非常低廉,它去掉了格式化所產(chǎn)生的系統(tǒng)消耗。當(dāng)某個(gè)觸發(fā)條件滿足時(shí),MemoryHandler將其緩沖的數(shù)據(jù)push(發(fā)布)到目標(biāo)Handler,由后者執(zhí)行實(shí)際的輸出。有三種模式觸發(fā)MemoryHandler進(jìn)行push操作:a、傳入的LogRecords的級別高于MemoryHandler預(yù)先定義的push級別;b、有其他對象顯式的調(diào)用其push方法;c、其子類重載了log方法,逐一檢索每個(gè)傳入的LogRecords,若符合特定的標(biāo)準(zhǔn)則進(jìn)行push操作

          實(shí)例:假設(shè)我們需要跟蹤一個(gè)生產(chǎn)環(huán)境中的一個(gè)很少出現(xiàn)的Bug。在大多數(shù)場合,系統(tǒng)化產(chǎn)生大量的日志記錄,而我們僅只關(guān)心記錄中最近的幾條,那么我們只需要使用MemoryHandler對日志記錄進(jìn)行緩存,當(dāng)且僅當(dāng)某個(gè)事件發(fā)生時(shí)將最近的幾條記錄從內(nèi)存中 dump到制定的文件中。

            //MemoryHandlerTest.java

          import java.util.logging.*;

          import java.io.*;

          public class MemoryHandlerTest {

             FileHandler fhandler;

             Logger logger;

             MemoryHandler mhandler;

            

             MemoryHandlerTest() {

                    try {

                           //構(gòu)造名為my.log的日志記錄文件

                        fhandler = new FileHandler("my.log");

                     int numRec = 5;

                     //構(gòu)造一個(gè)5個(gè)日志記錄的MemoryHandler,

          //其目標(biāo)Handler為一個(gè)FileHandler

                     mhandler = new MemoryHandler (fhandler, numRec, Level.OFF) ;

                      //構(gòu)造一個(gè)記錄器

                      logger = Logger.getLogger("com.mycompany");

                      //為記錄器添加一個(gè)MemoryHandler

                     logger.addHandler(mhandler);

           

                 } catch (IOException e) {

                 }

             }

             public static void main(String args[]) {

                    MemoryHandlerTest mt = new MemoryHandlerTest();

                    int trigger = (int)(Math.random()*100);

                  for (int i=1;i<100;i++) {

                         //在MemoryHandler中緩存日志記錄

                           mt.logger.log(Level.INFO,"日志記錄"+i);

                           if (i==trigger) {

                                  //觸發(fā)事件成立,顯式調(diào)用MemoryHandler的

          //push方法觸發(fā)目標(biāo)Handler輸出日志記錄到

          //my.log文件中

                                  mt.mhandler.push();

                                  break;

                           }

                    }

             }


          2、FileHandler 文件處理器

          StreamHandler流處理器將日志記錄以流的形式輸出。FileHandler、ConsoleHandler、SocketHandler為StreamHandler的子類。 ConsoleHandler將日志記錄輸出到控制終端。前面的實(shí)例(實(shí)例2除外)都將日記記錄數(shù)據(jù)輸出到控制臺。


          FileHandler將日志記錄輸出到特定的文件,或循環(huán)的幾個(gè)日志文件中。日志文件可以設(shè)置容量大小。當(dāng)日志文件達(dá)到限定的容量時(shí)將被自動清空,重頭開始寫入新的日志記錄數(shù)據(jù)。

          例:創(chuàng)建一個(gè)容量為1Mb的文件處理器

                                      int limit = 1000000; // 1 Mb

                                FileHandler fh = new FileHandler("my.log", limit, 1);

           

          對于循環(huán)的日志文件,每個(gè)文件將被指定容量限制。當(dāng)當(dāng)前的日志文件的長度達(dá)到制定值后該文件被關(guān)閉,新的日志文件被創(chuàng)建,舊的文件將在文件名模板后追加序號。如此產(chǎn)生多個(gè)順序編號的日志記錄文件。

          例:

          try {

                  // 創(chuàng)建一個(gè)擁有3個(gè)日志文件,每個(gè)容量為1Mb的文件處理器

                  String pattern = "my%g.log";

           int limit = 1000000; // 1 Mb

           int numLogFiles = 3;

            FileHandler fh = new FileHandler(pattern, limit, numLogFiles);

                  …

           } catch (IOException e) {

           }


          (四)Level

             Level對象定義了一組日志消息的級別,用于控制日志消息的輸出。一個(gè)級別對應(yīng)一個(gè)整型值。日志消息級別按照其整數(shù)值的大小排定優(yōu)先級。在Logger對象中設(shè)定一個(gè)級別,則大于等于該級別的日志消息將會被傳遞到某個(gè)Handler對象進(jìn)行輸出處理。

          J2SDK1.4的Java Logging框架中定義了以下消息級別:

          級別名稱

          int值(Windows 2000環(huán)境)

          OFF

          2147483647

          SEVERE

          1000

          WARNING

          900

          INFO

          800

          CONFIG

          700

          FINE

          500

          FINER

          400

          FINEST

          300

          ALL

          -2147483648

                                 表一

          Level.OFF具有最高的級別。將Logger的Level級別設(shè)置成Level.OFF

          讓我們看看消息級別是怎樣工作的:LoggingLevelTest.java

          import java.util.logging.*;

          public class LoggingLevelTest {

          public static void main(String args[]) {

               //使用Logger的靜態(tài)方法獲得一個(gè)匿名Logger

               Logger logger1 = Logger.getAnonymousLogger();

               //設(shè)置Logger對象記錄的最低日志消息級別

               logger1.setLevel(Level.FINER);

               //記錄消息

               logger1.severe("SEVERE級消息");

               logger1.warning("WARNING級消息");

               logger1.config("CONFIG級消息");

               logger1.info("INFO級消息");

               logger1.fine("FINE級消息");

              logger1.finer("FINER級消息");

               logger1.finest("FINEST級消息");

          }

          }

                           

          運(yùn)行結(jié)果:

          2003-1-15 7:02:03 LoggingLevelTest main

          服務(wù)器: SEVERE級消息

          2003-1-15 7:02:04 LoggingLevelTest main

          警告: WARNING級消息

          2003-1-15 7:02:04 LoggingLevelTest main

             配置: CONFIG級消息


          2003-1-15 7:02:04 LoggingLevelTest main

          信息: INFO級消息

           

          可以看出,優(yōu)先級低于INFO的日志消息不被記錄。

          Level的構(gòu)造函數(shù)為protected便于程序員開發(fā)自己的消息級別類。

           

          import java.util.logging.*;

          //自定義消息級別

          class myLevel extends Level{

               定義自己的消息級別SYSE

          p          public static final Level SYSE =
                      new myLevel("SYSE", Level.SEVERE.intValue()+10);

              public myLevel(String ln,int v) {

                  super(ln,v);

              }

          }

          public class MyLevelTest {

          public static void main(String args[]) {

              Logger logger1 = Logger.getAnonymousLogger();     

              //設(shè)置消息級別

              logger1.setLevel(myLevel.SYSE);

              //記錄消息

              logger1.log(myLevel.SYSE,"SYSE消息");

              logger1.severe("SVERE消息");

          }

          }  


          運(yùn)行結(jié)果:

                     

          2003-1-15 15:40:04 MyLevelTest main

          SYSE: SYSE消息

           

          只有SYSE消息被記錄,SVERE消息不被記錄,因?yàn)樽远x級別SYSE高于SEVERE.


          (五)Formatter
           
             Formatter負(fù)責(zé)對LogRecords進(jìn)行格式化。每個(gè)記錄處理器Handler同一個(gè)Formatter對象相關(guān)聯(lián)。Formatter對象接收從Handler傳來的LogRecord,將其格式化成字符串后返回給Handler進(jìn)行輸出。
           
             Formatter是一個(gè)抽象類。在J2SDK1.4中,其子類及它們之間的關(guān)系見圖二。
             自定義擴(kuò)展Formatter類。實(shí)例:MyFormatterTest.java

           

          import java.util.Date;

          import java.util.logging.*;

           

          //創(chuàng)建每條日志記錄以行的日志格式:

          //時(shí)間<空格>消息級別<空格>消息ID<空格>日志信息內(nèi)容<換行>

          class MyFormatter extends Formatter {

                 

                 public String format(LogRecord rec) {

                        StringBuffer buf = new StringBuffer(1000);

                  buf.append(new Date().toLocaleString()); //時(shí)間

                  buf.append(' ');

                  buf.append(rec.getLevel()); //消息級別

                  buf.append(' ');

                  buf.append(rec.getMillis()); //作為消息ID

                  buf.append(' ');

                  buf.append(formatMessage(rec));//格式化日志記錄數(shù)據(jù)

                  buf.append('\n');    //換行

                  return buf.toString();

              }      

          }

           

          public class MyFormatterTest {

                 public static void main(String args[]){

                        //創(chuàng)建記錄器

          Logger log1 = Logger.getLogger("MyLogger");

                        //創(chuàng)建記錄處理器

          Handler mh = new ConsoleHandler();

          //為記錄處理器設(shè)置Formatter

                        mh.setFormatter(new MyFormatter());

                        //為記錄器添加記錄處理器

          log1.addHandler(mh);

          //禁止消息處理將日志消息上傳給父級處理器

                        log1.setUseParentHandlers(false);

                        //記錄消息

                        log1.severe("消息1");

                        log1.warning("消息2");

                        log1.info("消息3");

                        log1.config("消息4");

                 }

          程序運(yùn)行結(jié)果:

           

          2003-1-15 16:59:38 SEVERE 1042621178968 消息1

          2003-1-15 16:59:40 WARNING 1042621178985 消息2

          2003-1-15 16:59:41 INFO 1042621179105 消息3

           

          三、配置文件

             J2SDK1.4的Java Logging框架的配置文件(Windows):

          %J2SDK1.4_HOME%/jre/lig/logging.properties

              從配置文件可以看到:

           (一) 自定義日志配置文件:

          java -Djava.util.logging.config.file=myfile

              (二)全局Handler在Java VM啟動時(shí)被加載。

           (二) 全局Handler默認(rèn)為java.util.logging.ConsoleHandler。

          handlers= java.util.logging.ConsoleHandler

          所以我們的任何日志記錄動作都會在控制臺進(jìn)行顯示。

           (三) 缺省的消息記錄級別為:INFO

          .level= INFO 

          在缺省情況下我們在控制臺看不見低于INFO級別的日志消息。

           (四) 缺省的Handler消息格式為java.util.logging.SimpleFormatter

           

          四、日志框架在程序測試中的應(yīng)用

              Logger類提供了兩個(gè)的方法:Logger.entering() Logger.exiting() 。這對我們調(diào)試自己的方法調(diào)用提供了便利的方式。

              例子:

              記錄方法調(diào)用的輸入?yún)?shù)和輸出參數(shù) 方法myMethod將一個(gè)int 追加在一個(gè)對象之后。

          運(yùn)行該程序應(yīng)將logging.properties的

          java.util.logging.ConsoleHandler.level = INFO

          改為:

          java.util.logging.ConsoleHandler.level = ALL

           

          import java.util.logging.*;

          public class MyClass {

                  public String myMethod(int p1, Object p2) {

                      Logger logger = Logger.getLogger("com.mycompany.MyClass");

                      if (logger.isLoggable(Level.FINER)) {

                          logger.entering(this.getClass().getName(), "myMethod",

                                          new Object[]{new Integer(p1), p2});

                      }

                      String tmp = p2.toString() + p1;

           

                      if (logger.isLoggable(Level.FINER)) {

                          logger.exiting(this.getClass().getName(), "myMethod", tmp);

                      }

                      return tmp;

                  }

                  

                  public static void main(String args[]) {

                      MyClass mc = new MyClass();

                      String rslt = mc.myMethod(123,"Hello");

                     

                  }

          }

           

          后記

                 J2SDK1.4引入的日志記錄框架為構(gòu)建簡易的日志記錄系統(tǒng)提供了便利的解決方案。雖然還有期它的一些專用日志包如Log4j,但從簡單的打印輸出到嚴(yán)密的、可擴(kuò)展的日志記錄框架,J2SDK1.4的日志系統(tǒng)已經(jīng)足以滿足一般的系統(tǒng)開發(fā)的要求。

           



          Author: orangelizq
          email: orangelizq@163.com

          歡迎大家訪問我的個(gè)人網(wǎng)站 萌萌的IT人
          posted on 2007-07-22 15:07 桔子汁 閱讀(406) 評論(0)  編輯  收藏 所屬分類: J2EE
          主站蜘蛛池模板: 如东县| 郓城县| 庆城县| 交口县| 上蔡县| 沅江市| 沈丘县| 高青县| 淮南市| 静海县| 连山| 大渡口区| 墨脱县| 江永县| 牡丹江市| 巍山| 荥经县| 金坛市| 武冈市| 五常市| 乐昌市| 前郭尔| 南康市| 达日县| 内黄县| 金阳县| 山东省| 五指山市| 嘉定区| 巨野县| 富顺县| 凤冈县| 中卫市| 张家川| 旬阳县| 岳池县| 黄山市| 鸡西市| 宁晋县| 棋牌| 格尔木市|