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

作者:qingkangxu 时间:2022-05-11 06:33:15 

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

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

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

2,希望完全定制log的format  

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

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

引言

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

if not exist "%CATALINA_HOME%\bin\tomcat-juli.jar" goto noJuli
set JAVA_OPTS=%JAVA_OPTS% -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties"

当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的一大目的。

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的第二大目的。

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做了缓存以提高效率。

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对日志进行格式化。

来源:https://blog.csdn.net/qingkangxu/article/details/84225566

标签:LogManager,logger,自定义
0
投稿

猜你喜欢

  • Java一个简单的红包生成算法

    2023-12-12 10:56:50
  • Java后台线程操作示例【守护线程】

    2023-11-25 01:35:44
  • Android实现双曲线折线图

    2023-07-29 15:07:28
  • java如何利用FastJSON、Gson、Jackson三种Json格式工具自定义时间序列化

    2023-04-01 07:25:00
  • java在网页上面抓取邮件地址的方法

    2023-10-01 19:18:21
  • SpringBoot中使用Session共享实现分布式部署的示例代码

    2022-10-17 04:27:54
  • 利用Kotlin + Spring Boot实现后端开发

    2022-02-20 05:30:19
  • Java Calendar类使用案例详解

    2023-07-09 14:03:22
  • 如何使用lamda表达式对list进行求和

    2022-08-24 09:20:09
  • Android四种数据存储的应用方式

    2023-07-25 05:01:06
  • java实现顺时针打印矩阵

    2023-06-26 19:17:22
  • Android如何判断一个点在不在多边形区域内

    2023-07-06 14:06:13
  • Java通过PropertyDescriptor反射调用set和get方法

    2023-10-11 19:34:17
  • winform导出dataviewgrid数据为excel的方法

    2023-09-25 15:01:38
  • Java适配器模式_动力节点Java学院整理

    2021-09-06 10:50:53
  • C# 正则判断一个数字的格式是否有逗号的代码

    2021-09-13 07:42:12
  • SpringMVC上传文件的两种方法

    2023-11-17 14:27:20
  • Java接口继承和使用接口操作示例

    2023-11-29 05:08:43
  • Java8新特性之默认方法(default)浅析

    2023-10-03 10:41:13
  • Java 并发编程学习笔记之Synchronized简介

    2023-09-01 03:24:05
  • asp之家 软件编程 m.aspxhome.com