通過自定制LogManager實(shí)現(xiàn)程序完全自定義的logger
前一篇博文介紹了JDK logging基礎(chǔ)知識(shí)
博文中也提到LogManager,本章主要闡述怎么完全定制化LogManager來實(shí)現(xiàn)應(yīng)用程序完全自定制的logger,其實(shí)對(duì)于大多數(shù)開發(fā)者來說,很少有需要定制LogManager的時(shí)候,只有是需要單獨(dú)開發(fā)一個(gè)產(chǎn)品,需要完全獨(dú)立的logger機(jī)制時(shí)才有可能需要定制LogManager,比如:
1,希望自由定制log的輸出路徑
2,希望完全定制log的format
3,希望日志中的國(guó)際化信息采用自己定義的一套機(jī)制等
當(dāng)然,對(duì)于大型的中間件而言,自定義LogManager則是非常有必要的。
引言
對(duì)tomcat熟悉的讀者,有可能會(huì)注意到tomcat的啟動(dòng)腳本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"
當(dāng)tomcat的bin路徑下存在tomcat-juli.jar文件(也就是存在定制的LogManager)時(shí),那么會(huì)強(qiáng)制在JVM系統(tǒng)屬性中指定org.apache.juli.ClassLoaderLogManager作為整個(gè)JVM的LogManager,以此來完成一些特殊操作。
websphere的啟動(dòng)腳本startServer.bat中也定義了自己的LogManager,如下:
java.util.logging.manager=com.ibm.ws.bootstrap.WsLogManager
怎么實(shí)現(xiàn)自定義的LogManager
首先要實(shí)現(xiàn)一個(gè)繼承自java.util.logging.LogManager的類:
子類覆蓋java.util.logging.LogManager的addLogger方法,在成功添加logger之后對(duì)logger做定制化操作,從代碼中可以看出addLogger方法調(diào)用了子類的internalInitializeLogger方法,internalInitializeLogger方法中先清空l(shuí)ogger的所有handler,然后再增加一個(gè)自定義的Handler
需要說明一下:internalInitializeLogger方法中的操作(給logger增設(shè)我們自定義的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是一個(gè)把logger日志輸出到文件中的handler,可以通過com.bes.instanceRoot系統(tǒng)屬性來指定日志文件跟路徑;其次,ServerFileHandler也指定了自己的UniformLogFormatter;最后是需要覆蓋父類的publish方法,覆蓋的publish方法在做真正的日志輸入之前會(huì)檢查日志文件是否存在,然后就是創(chuàng)建一個(gè)和日志文件對(duì)應(yīng)的輸出流,把該輸出流設(shè)置為ServerFileHandler的輸出流以至日志輸出的時(shí)候能輸出到文件中。另外,WrapperStream僅僅是一個(gè)流包裝類。
這里也需要說一下:ServerFileHandler構(gòu)造方法中的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 = "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;
}
}實(shí)現(xiàn)Formatter
之前已經(jīng)提到過,使用logger日志輸出的時(shí)候,handler會(huì)自動(dòng)調(diào)用自己的formatter對(duì)日志做format,然后輸出格式化之后的日志。自定義的Formatter只需要覆蓋public String format(LogRecord record)便可。這個(gè)類本身很簡(jiǎn)單,就是日志輸出時(shí)自動(dòng)增加指定格式的時(shí)間,加上分隔符,對(duì)日志進(jìn)行國(guó)際化處理等操作。 需要注意的是類中對(duì)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之后,在啟動(dòng)JVM的命令中增加系統(tǒng)屬性便可
java -Djava.util.logging.manager=com.bes.logging.ServerLogManager
加上這個(gè)系統(tǒng)屬性之后通過java.util.logging.Logger類獲取的logger都是經(jīng)過定制的LogManager作為初始化的,日志輸出的時(shí)候便會(huì)使用上面的ServerFileHandler#publish()方法進(jìn)行日志輸出,并使用UniformLogFormatter對(duì)日志進(jìn)行格式化。
以上就是通過自定制LogManager實(shí)現(xiàn)程序完全自定義的logger的詳細(xì)內(nèi)容,更多關(guān)于自定制LogManager實(shí)現(xiàn)自定義logger的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用kotlin集成springboot開發(fā)的超詳細(xì)教程
目前大多數(shù)都在使用java集成 springboot進(jìn)行開發(fā),本文演示僅僅將 java換成 kotlin,其他不變的情況下進(jìn)行開發(fā),需要的朋友可以參考下2021-09-09
ArrayList源碼探秘之Java動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)
這篇文章將帶大家從ArrayList源碼來探秘一下Java動(dòng)態(tài)數(shù)組的實(shí)現(xiàn),文中的示例代碼講解詳細(xì),對(duì)我們深入了解JavaScript有一定的幫助,需要的可以參考一下2023-08-08
一個(gè)簡(jiǎn)單的Python名片管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了一個(gè)簡(jiǎn)單的Python名片管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
Java實(shí)現(xiàn)的簡(jiǎn)單字符串反轉(zhuǎn)操作示例
這篇文章主要介紹了Java實(shí)現(xiàn)的簡(jiǎn)單字符串反轉(zhuǎn)操作,結(jié)合實(shí)例形式分別描述了java遍歷逆序輸出以及使用StringBuffer類的reverse()方法兩種字符串反轉(zhuǎn)操作技巧,需要的朋友可以參考下2018-08-08
mybatis模糊查詢之bind標(biāo)簽和concat函數(shù)用法詳解
大家都知道bind 標(biāo)簽可以使用 OGNL 表達(dá)式創(chuàng)建一個(gè)變量井將其綁定到上下文中,接下來通過本文給大家介紹了mybatis模糊查詢——bind標(biāo)簽和concat函數(shù)用法,需要的朋友可以參考下2022-08-08
Mybatis 入門之MyBatis環(huán)境搭建(第一篇)
Mybatis的前身叫iBatis,本是apache的一個(gè)開源項(xiàng)目, 2010年這個(gè)項(xiàng)目由apache software foundation 遷移到了google code,并且改名為MyBatis。這篇文章主要介紹了Mybatis入門第一篇之MyBaits環(huán)境搭建,需要的朋友參考下2016-12-12
Java中的SynchronousQueue阻塞隊(duì)列使用代碼實(shí)例
這篇文章主要介紹了Java中的SynchronousQueue阻塞隊(duì)列使用代碼實(shí)例,SynchronousQueue是無緩沖區(qū)的阻塞隊(duì)列,即不能直接向隊(duì)列中添加數(shù)據(jù),會(huì)報(bào)隊(duì)列滿異常,需要的朋友可以參考下2023-12-12

