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

网友投稿 343 2022-08-18


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

目录引言怎么实现自定义的LogManager自定义的LogManager中使用到的ServerFileHandler实现Formatter

前一篇博文介绍了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.http://util.logging.StreamHandler;

public class ServerFileHandler extends StreamHandler {

private WrapperStream wrappedStream;

private String absoluteFileName = null;

static final String LOG_FILENAME_PREFIX = "server";

static final String LOG_FILENAME_SUFFIX = ".log";

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(

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

}

}

super.publish(record);

flush();

}

public String createFileName() {

String instDir = "";

instDir = System.getProperty("com.bes.instanceRoot");

if(instDir == null || "".equals(instDir)){

instDir = ".";

}

return instDir + "/" + 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 && !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("line.separator");

private static final SimpleDateFormat dateFormatter = new SimpleDateFormat(

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

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 = "The log message is null.";

}

if (logMessage.indexOf("{0}") >= 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 "Log error occurred on msg: " + record.getMessage() + ": "

+ 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的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:BeanDefinitionRegistryPostProcessor如何动态注册Bean到Spring
下一篇:springboot1.X和2.X中如何解决Bean名字相同时覆盖
相关文章

 发表评论

暂时没有评论,来抢沙发吧~