通过自定制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.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对日志进行格式化。

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

(0)

相关推荐

  • log4j2的异步使用及添加自定义参数方式

    目录 log4j2异步使用及添加自定义参数 添加依赖(这里省略了版本号) 下面写一个生产可用的log4j2.xml的模板 补充知识 自定义日志格式 如何在日志中添加自己想传的参数? log4j 输入自定义参数 测试代码如下 log4j2异步使用及添加自定义参数 关于log4j2的性能和原理就不赘述了,这篇主要讲使用,配置文件解读,和添加自定义参数,偏应用的一篇文章. 相比与其他的日志系统,log4j2丢数据这种情况少:disruptor技术,在多线程环境下,性能高于logback等10倍以上:利

  • java自定义日志输出文件(log4j日志文件输出多个自定义日志文件)

    log4j输出多个自定义日志文件 如果在实际应用中需要输出独立的日志文件,怎样才能把所需的内容从原有日志中分离,形成单独的日志文件呢? 先看一个常见的log4j.properties文件,它是在控制台和test.log文件中记录日志: 复制代码 代码如下: log4j.rootLogger=DEBUG, stdout, logfile log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layo

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

    目录 引言 怎么实现自定义的LogManager 自定义的LogManager中使用到的ServerFileHandler 实现Formatter 前一篇博文介绍了JDK logging基础知识 博文中也提到LogManager,本章主要阐述怎么完全定制化LogManager来实现应用程序完全自定制的logger,其实对于大多数开发者来说,很少有需要定制LogManager的时候,只有是需要单独开发一个产品,需要完全独立的logger机制时才有可能需要定制LogManager,比如: 1,希望自

  • 微信小程序开发自定义tabBar实战案例(定制消息99+小红心)

    目录 一.前提概要 二. 动态显示info消息 三. 页面切换效果 四. 配置总结 一.前提概要 效果:实现一个自定义tabBar,使消息tabBar能够显示消息数量,并通过全局共享的方式,控制消息数量 需要的知识点如下: mobx辅助库(全局共享,见文章) vant组件库(见文章) 组件的behavior (见文章) 自定义组件 样式隔离 组件数据监听器 自定义组件主要分为三个步骤(许多实例实现步骤差不多流程) 配置信息 (几乎每个要实现的都需要这一步) 创建自定义组件代码文件 编写代码 详细

  • 微信小程序tabBar自定义弹窗遮挡不住解决技巧

    目录 背景 方法一:自定义tabbar 方法二:套用原生提供的tabbar自定义功能,嵌套自己的tabbar组件 总结 成果 背景 最近开发微信小程序发现一个问题,就是微信小程序官方自带的tabbar层级很高,自定义modal盖不住tabbar的问题:翻阅官方的社区,很多同学说 设置z-index:99999:可以解决这个问题,我实践下来是有问题的:ios是解决不了问题:经过反复实践有两种方法可以解决问题: 方法一:自定义tabbar 这个方法就是完全放弃微信官方的tabbar:自己用SPA的方

  • 微信小程序 Toast自定义实例详解

    微信小程序 Toast自定义实例详解 实现类似于Android的Toast提示 index.js: var timer; var inputinfo = ""; var app = getApp() Page({ data: { animationData:"", showModalStatus:false }, onLoad: function () { }, showModal: function () { // 显示遮罩层 var animation = wx

  • 小程序实现自定义导航栏适配完美版

    1.发现问题 小程序页面自定义导航栏功能已经开放有些日子了(还不知道这个功能的可以先>>了解一下),这极大的提升了小程序开发的自由度,相信不少小伙伴已经使用过这个功能,同时也相信不少小伙伴在此功能开发过程中踩过同样的一些坑: 机型多如牛毛:自定义导航栏高度在不同机型始终无法达到视觉上的统一: 调皮的胶囊按钮:导航栏元素(文字,图标等)怎么也对不齐那该死的胶囊按钮: 各种尺寸的全面屏,奇怪的刘海屏,简直要抓狂. 同样的,这些问题也是小灰经历过的.但是小灰相信,办法总比问题多,于是开始了自己的探究

  • 微信小程序之自定义组件的实现代码(附源码)

    最近在项目开发中,遇到好多雷同的页面样式,就想着可以将常用的功能模块封装成组件,方便在项目中使用和修改,下面就参照微信小程序的文档分步骤写一个微信小程序的组件. 附上效果图: step1:创建文件并申明 与创建微信小程序的页面一样,一个自定义组件也需要json,wxml,wxss,js四个文件. 在项目根目录中创建文件夹,取名为:component,在该目录下继续创建文件夹successModal. 可以在开发工具中右键创建,选择component,默认自动会创建四个文件.如图: 在succes

  • 微信小程序封装自定义弹窗的实现代码

    最近在做小程序的登录,需要同时获取用户手机号和头像昵称等信息,但是小程序又不支持单个接口同时获取两种数据,因此想到自定义一个弹窗,通过弹窗按钮触发获取手机号事件.记录一下. 具体代码如下: 业务代码中: 在业务代码中引入dialog组件即可 <dialog visible="{{dialogVisible}}" showFooter="{{footerVisible}}" title="测试一下"> <view class='d

  • 微信小程序实现自定义加载图标功能

    效果图 实现思路 1.首先通过HTML+CSS实现加载动画的静态效果: 2.根据需求给每个动画设计不同的动画效果. 例如第一个加载图标的静态绘制 1.首先确定动画的盒子宽高: 2.设置盒子中每一个长方形的宽高以及定位(注意:此处需要将长方形的旋转中心点移动到长方形的右侧边终点,方便后期以该点旋转.): 3.通过长方形盒子的伪元素,设置显示的长方形背景和宽高,同时进行定位. 4.由于在第二步的时候,已经将旋转中心移动,此处我们直接对每一个盒子中长方形进行旋转(注意:旋转角度 = 360 / 盒子中

  • 微信小程序实现自定义modal弹窗封装的方法

    前言 小程序官方提供了 wx.showModal 方法,但样式比较固定,不能满足多元化需求,自定义势在必行~ 老规矩先上图 点击某个按钮,弹出 modal框,里面的内容可以自定义,可以是简单的文字提示,也可以输入框等复杂布局.操作完点击取消或确定关闭 modal. 如何使用 将下面的 modal.wxml .modal.wxss .modal.js .modal.json 四个文件复制到对应位置即可. 封装完之后调用起来也很简单,看看调用的代码吧 <modal show="{{showMo

  • 小程序实现自定义多层级单选和多选

    本文实例为大家分享了小程序实现自定义多层级单选和多选的具体代码,供大家参考,具体内容如下 效果: ps:这儿是用自定义的下拉框,我把它封装成了一个组件 wxml <view class="select-box"> <view class="select-title"> <view class="cell-border"> <van-field value="{{ layout }}"

随机推荐