好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

通过自定制LogManager实现程序完全自定义的logger

前一篇博文介绍了 JDK logging基础知识 

博文中也提到LogManager,本章主要阐述怎么完全定制化LogManager来实现应用程序完全自定制的logger,其实对于大多数开发者来说,很少有需要定制LogManager的时候,只有是需要单独开发一个产品,需要完全独立的logger机制时才有可能需要定制LogManager,比如:

1,希望自由定制log的输出路径 

2,希望完全定制log的format  

3,希望日志中的国际化信息采用自己定义的一套机制等

当然,对于大型的中间件而言,自定义LogManager则是非常有必要的。

引言

对tomcat熟悉的读者,有可能会注意到tomcat的启动脚本catalina.bat中也使用定制的LogManager,如下:

?

1

2

if not exist &quot;%CATALINA_HOME%\bin\tomcat-juli.jar&quot; goto noJuli

set JAVA_OPTS=%JAVA_OPTS% -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config. file =&quot;%CATALINA_BASE%\conf\logging.properties&quot;

当tomcat的bin路径下存在tomcat-juli.jar文件(也就是存在定制的LogManager)时,那么会强制在JVM系统属性中指定org.apache.juli.ClassLoaderLogManager作为整个JVM的LogManager,以此来完成一些特殊操作。

websphere的启动脚本startServer.bat中也定义了自己的LogManager,如下:

java.util.logging.manager=com.ibm.ws.bootstrap.WsLogManager

怎么实现自定义的LogManager

首先要实现一个继承自java.util.logging.LogManager的类:

子类覆盖java.util.logging.LogManager的addLogger方法,在成功添加logger之后对logger做定制化操作,从代码中可以看出addLogger方法调用了子类的internalInitializeLogger方法,internalInitializeLogger方法中先清空logger的所有handler,然后再增加一个自定义的Handler

需要说明一下:internalInitializeLogger方法中的操作(给logger增设我们自定义的handler)是我们自定义LogManager的一大目的。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

package com.bes.logging;

import java.util.logging.Handler;

import java.util.logging.Level;

import java.util.logging.LogManager;

import java.util.logging.Logger;

public class ServerLogManager extends LogManager {

     private static ServerFileHandler handlerSingleton;

     private static ServerLogManager thisInstance;

     private Object lockObj = new Object();

     public ServerLogManager() {

         super ();

     }

     public static synchronized ServerLogManager getInstance() {

         if (thisInstance == null ) {

             thisInstance = new ServerLogManager();

         }

         return thisInstance;

     }

     public boolean addLogger(Logger logger) {

         boolean result = super .addLogger(logger);

          //initialize Logger

         if (logger.getResourceBundleName() == null ) {

             try {

                 Logger newLogger = Logger.getLogger(logger.getName(),

                         getLoggerResourceBundleName(logger.getName()));

                 assert (logger == newLogger);

             } catch (Throwable ex) {

                 //ex.printStackTrace();

             }

         }

         synchronized (lockObj) {

             internalInitializeLogger(logger);

         }

         return result;

     }

     /**

      * Internal Method to initialize a list of unitialized loggers.

      */

     private void internalInitializeLogger( final Logger logger) {

         // Explicitly remove all handlers.

         Handler[] h = logger.getHandlers();

         for ( int i = 0 ; i < h.length; i++) {

             logger.removeHandler(h[i]);

         }

         logger.addHandler(getServerFileHandler());

         logger.setUseParentHandlers( false );

         logger.setLevel(Level.FINEST); // only for test

     }

     private static synchronized Handler getServerFileHandler() {

         if (handlerSingleton == null ) {

             try {

                 handlerSingleton = ServerFileHandler.getInstance();

                 handlerSingleton.setLevel(Level.ALL);

             } catch (Exception e) {

                 e.printStackTrace();

             }

         }

         return handlerSingleton;

     }

     public String getLoggerResourceBundleName(String loggerName) {

         String result = loggerName + "." + "LogStrings" ;

         return result;

     }

}

自定义的LogManager中使用到的ServerFileHandler

如下:

该ServerFileHandler是一个把logger日志输出到文件中的handler,可以通过com.bes.instanceRoot系统属性来指定日志文件跟路径;其次,ServerFileHandler也指定了自己的UniformLogFormatter;最后是需要覆盖父类的publish方法,覆盖的publish方法在做真正的日志输入之前会检查日志文件是否存在,然后就是创建一个和日志文件对应的输出流,把该输出流设置为ServerFileHandler的输出流以至日志输出的时候能输出到文件中。另外,WrapperStream仅仅是一个流包装类。

这里也需要说一下:ServerFileHandler构造方法中的setFormatter(new UniformLogFormatter());操作是我们自定义LogManager的第二大目的。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

package com.bes.logging;

import java.io.BufferedOutputStream;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.OutputStream;

import java.util.logging.LogRecord;

import java.util.logging.StreamHandler;

public class ServerFileHandler extends StreamHandler {

   private WrapperStream wrappedStream;

   private String absoluteFileName = null ;

   static final String LOG_FILENAME_PREFIX = &quot;server&quot;;

   static final String LOG_FILENAME_SUFFIX = &quot;.log&quot;;

   private String logFileName = LOG_FILENAME_PREFIX + LOG_FILENAME_SUFFIX;

   public static final ServerFileHandler thisInstance = new ServerFileHandler();

   public static synchronized ServerFileHandler getInstance() {

     return thisInstance;

   }

   protected ServerFileHandler() {

     try {

       setFormatter( new UniformLogFormatter());

     } catch (Exception e) {

       e.printStackTrace();

     }

   }

   public synchronized void publish(LogRecord record) {

     if (wrappedStream == null ) {

       try {

         absoluteFileName = createFileName();

         openFile(absoluteFileName);

       } catch (Exception e) {

         throw new RuntimeException(

             &quot;Serious Error Couldn't open Log File&quot; + e);

       }

     }

     super .publish(record);

     flush();

   }

   public String createFileName() {

     String instDir = &quot;&quot;;

     instDir = System.getProperty(&quot;com.bes.instanceRoot&quot;);

     if (instDir == null || &quot;&quot;.equals(instDir)){

       instDir = &quot;.&quot;;

     }

     return instDir + &quot;/&quot; + getLogFileName();

   }

   /**

    * Creates the file and initialized WrapperStream and passes it on to

    * Superclass (java.util.logging.StreamHandler).

    */

   private void openFile(String fileName) throws IOException {

     File file = new File(fileName);

     if (!file.exists()){

       if (file.getParentFile() != null &amp;&amp; !file.getParentFile().exists()){

         file.getParentFile().mkdir();

       }

       file.createNewFile();

     }

     FileOutputStream fout = new FileOutputStream(fileName, true );

     BufferedOutputStream bout = new BufferedOutputStream(fout);

     wrappedStream = new WrapperStream(bout, file.length());

     setOutputStream(wrappedStream);

   }

   private class WrapperStream extends OutputStream {

     OutputStream out;

     long written;

     WrapperStream(OutputStream out, long written) {

       this .out = out;

       this .written = written;

     }

     public void write( int b) throws IOException {

       out.write(b);

       written++;

     }

     public void write( byte buff[]) throws IOException {

       out.write(buff);

       written += buff.length;

     }

     public void write( byte buff[], int off, int len) throws IOException {

       out.write(buff, off, len);

       written += len;

     }

     public void flush() throws IOException {

       out.flush();

     }

     public void close() throws IOException {

       out.close();

     }

   }

   protected String getLogFileName() {

     return logFileName;

   }

}

实现Formatter

之前已经提到过,使用logger日志输出的时候,handler会自动调用自己的formatter对日志做format,然后输出格式化之后的日志。自定义的Formatter只需要覆盖public String format(LogRecord record)便可。这个类本身很简单,就是日志输出时自动增加指定格式的时间,加上分隔符,对日志进行国际化处理等操作。 需要注意的是类中对ResourceBundle做了缓存以提高效率。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

package com.bes.logging;

import java.text.MessageFormat;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.HashMap;

import java.util.ResourceBundle;

import java.util.logging.Formatter;

import java.util.logging.LogManager;

import java.util.logging.LogRecord;

public class UniformLogFormatter extends Formatter {

   private Date date = new Date();

   private HashMap loggerResourceBundleTable;

   private LogManager logManager;

   private static final char FIELD_SEPARATOR = '|' ;

   private static final String CRLF = System.getProperty(&quot;line.separator&quot;);

   private static final SimpleDateFormat dateFormatter = new SimpleDateFormat(

       &quot;yyyy-MM-dd 'T' HH:mm:ss.SSSZ&quot;);

   public UniformLogFormatter() {

     super ();

     loggerResourceBundleTable = new HashMap();

     logManager = LogManager.getLogManager();

   }

   public String format(LogRecord record) {

     return uniformLogFormat(record);

   }

   private String uniformLogFormat(LogRecord record) {

     try {

       String logMessage = record.getMessage();

       int msgLength = 150 ; // typical length of log record

       if (logMessage != null )

         msgLength += logMessage.length();

       StringBuilder recordBuffer = new StringBuilder(msgLength);

       // add date to log

       date.setTime(record.getMillis());

       recordBuffer.append(dateFormatter.format(date)).append(

           FIELD_SEPARATOR);

       // add log level and logger name to log

       recordBuffer.append(record.getLevel()).append(FIELD_SEPARATOR);

       recordBuffer.append(record.getLoggerName()).append(FIELD_SEPARATOR);

       if (logMessage == null ) {

         logMessage = &quot;The log message is null .&quot;;

       }

       if (logMessage.indexOf(&quot;{ 0 }&quot;) >= 0 ) {

         try {

           logMessage = java.text.MessageFormat.format(logMessage,

               record.getParameters());

         } catch (Exception e) {

           // e.printStackTrace();

         }

       } else {

         ResourceBundle rb = getResourceBundle(record.getLoggerName());

         if (rb != null ) {

           try {

             logMessage = MessageFormat.format(

                 rb.getString(logMessage),

                 record.getParameters());

           } catch (java.util.MissingResourceException e) {

           }

         }

       }

       recordBuffer.append(logMessage);

       recordBuffer.append(CRLF);

       return recordBuffer.toString();

     } catch (Exception ex) {

       return &quot;Log error occurred on msg: &quot; + record.getMessage() + &quot;: &quot;

           + ex;

     }

   }

   private synchronized ResourceBundle getResourceBundle(String loggerName) {

     if (loggerName == null ) {

       return null ;

     }

     ResourceBundle rb = (ResourceBundle) loggerResourceBundleTable

         .get(loggerName);

     if (rb == null ) {

       rb = logManager.getLogger(loggerName).getResourceBundle();

       loggerResourceBundleTable.put(loggerName, rb);

     }

     return rb;

   }

}

 完成了定制的LogManager之后,在启动JVM的命令中增加系统属性便可

java -Djava.util.logging.manager=com.bes.logging.ServerLogManager

加上这个系统属性之后通过java.util.logging.Logger类获取的logger都是经过定制的LogManager作为初始化的,日志输出的时候便会使用上面的ServerFileHandler#publish()方法进行日志输出,并使用UniformLogFormatter对日志进行格式化。

以上就是通过自定制LogManager实现程序完全自定义的logger的详细内容,更多关于自定制LogManager实现自定义logger的资料请关注其它相关文章!

原文链接:https://blog.csdn.net/qingkangxu/article/details/84225566

查看更多关于通过自定制LogManager实现程序完全自定义的logger的详细内容...

  阅读:14次