通过自定制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 = "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对日志进行格式化。
来源:https://blog.csdn.net/qingkangxu/article/details/84225566


猜你喜欢
- 兄dei,耐心把我的写的看完,我写的不繁琐,很好理解.IDEA插件之Mybatis Log plugin
- 在Java中我们知道静态变量会在类加载时机的“初始化”阶段得到赋值(编译器会收集类中的静态变量及静态
- 项目常常需要有访问共享文件夹的需求,例如共享文件夹存储照片、文件等。那么如何使用Java读写Windows共享文件夹呢?Java可以使用JC
- 一、先来看看效果演示二、实现原理:这个其实不难实现,通过一个定时器不断调用TextView的setText就行了,在setText的时候播放
- 1.在C#中,类名首字母需大写。如:class Student2.在C#中Main()方法有四种形式:static void Main(st
- Android 文件读写操作方法总结在Android中的文件放在不同位置,它们的读取方式也有一些不同。本文对android中对资源文件的读取
- OAuth2简介OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个
- 前言:项目中数据分页是一个很常见的需求,目前大部分项目都会使用pagehelper进行分页,那么在使用的过程中是否考虑如下问题?一、基本集成
- Beanutils.copyProperties()用法及重写提高效率特别说明本文介绍的是Spring(import org.springf
- 1、概念:MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一
- 一、简介Spring Cloud Config为分布式系统中的配置提供服务器端和客户端支持。可以集中管理所有环境中应用程序的配置文件。其服务
- 相信大家都见到了微信图标颜色渐变的过程,是不是感觉很牛逼?不得不说微信团队确实是很厉害的团队,不管是从设计还是开发人员。今天我带大家来看看,
- 前言本文准确来讲是探讨如何用 Jackson 来序列化 Apache avro 对象,因为简单用 Jackson 来序列化 Apache a
- AOP注解无效,切面不执行的解决想做一个api请求日志,想到使用aop,配置过程中遇到了一个坑,aop不起作用,我的aop是这样的:pack
- C# WinForm控件的拖动和缩放是个很有用的功能。实现起来其实很简单的,主要是设计控件的MouseDown、MouseLeave、Mou
- 生产者工程POM依赖可以在创建工程时直接选择添加依赖。application文件因为rabbitmq具有默认地址及用户信息,所以如果是本地r
- 本文实例为大家分享了Unity3D Shader实现镜子效果的具体代码,供大家参考,具体内容如下/p>Shader部分代码:Shade
- 前言spring中解析元素最重要的一个对象应该就属于 BeanDefinition了;这个Spring容器中最基本的内部数据结构;它让xml
- Android文件存储看下网上随处可以搜到的文件存储套路if(Environment.MEDIA_MOUNTED.equals(Enviro
- Eclipse ADT的Custom debug keystore自定义调试证书的时候,Android应用开发接入各种SDK时会发现,有很多