• 写在前面的话
log4j支持自定义的输出。所有的输出都实现了自Appender接口。一般来说,自定义输出值需要继承AppenderSkeleton类,并实现几个方法就可以了。

写这篇博客,我主要也是想说,框架之所有被成为是一个框架,是在帮我们完成大部分的通用代码,这就有一个前提就是说它必须要有具有良好的扩张性。方便每一个使用者来扩展,当然我们也可以根据自己的喜好去改人家框架的源码,但是最实在的也是最有效的去扩展人家开源框架,在扩展的时候我们也可以参照人家原来的默认实现,这样子对于我们的学习也是一大进步。

  • 一个自定义输出的例子
OK,废话不说了,现在我们开始吧。先来看一个自定义输出的例子,CountingConsoleAppender跟控制台输出类似,不同的是会统计日志输出的次数。当输出次数超出预定的值时,会做相应的业务处理,这里简单的为打印出一行提示信息,并停止输出。代码如下:
package org.linkinpark.commons.logtest;import java.util.Objects;import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.spi.LoggingEvent;public class CountingConsoleAppender extends AppenderSkeleton
{protected int count = 0;protected int limit = 10;/*** 关闭资源*/@Overridepublic void close(){if (this.closed){return;}this.closed = true;}/*** 这里需要使用格式化器*/@Overridepublic boolean requiresLayout(){return true;}@Overrideprotected void append(LoggingEvent event){// 1,验证,如果没有格式化器,报错,如果次数超过限制,报错if (this.layout == null){errorHandler.error("没有设置[" + name + "]日志格式化器。", null, ErrorCode.MISSING_LAYOUT);return;}if (count >= limit){errorHandler.error("输出次数[" + limit + "]达到了[" + getName() + "]的上限。", null, ErrorCode.WRITE_FAILURE);return;}// 控制台打印日志System.out.println(this.layout.format(event));// 如果配置的格式化器没有处理异常,这里打印异常栈信息if (layout.ignoresThrowable()){String[] throwableStrRep = event.getThrowableStrRep();if (Objects.nonNull(throwableStrRep)){for (String throwStr : throwableStrRep){System.out.println(throwStr);}}}// 打印日志结束,修改打印次数count++;}public int getCount(){return count;}public CountingConsoleAppender setCount(int count){this.count = count;return this;}public int getLimit(){return limit;}public void setLimit(int limit){this.limit = limit;}}

配置文件如下:

#定义输出等级和输出appender
log4j.rootLogger=DEBUG,countingconsole
log4j.appender.countingconsole=org.linkinpark.commons.logtest.CountingConsoleAppender
#设置输出样式
log4j.appender.countingconsole.layout=org.apache.log4j.PatternLayout
#日志输出信息格式为
log4j.appender.countingconsole.layout.ConversionPattern=[%-d{yyyy-MM-dd HH:mm:ss}]-[%t-%5p]-[%C-%M(%L)]: %m%n
#控制最大输出次数
log4j.appender.countingconsole.limit=3
#打开4j本身的日志输出
log4j.debug=true

OK,现在我们来运行下测试看下控制台输出情况,测试代码如下:

package org.linkinpark.commons.logtest;import org.apache.log4j.Logger;
import org.junit.Test;/*** @创建作者: LinkinPark* @创建时间: 2016年2月23日* @功能描述: 测试自己扩展的CountConsoleAppender*/
public class Log4jTest
{public static Logger log = Logger.getLogger(Log4jTest.class);@Testpublic void logTest(){log.debug("debug级别的日志输出");log.debug("debug级别的日志输出1");log.debug("debug级别的日志输出2");log.debug("debug级别的日志输出3");}}

测试绿条,控制台输出如下:

log4j: Parsing for [root] with value=[DEBUG,countingconsole].
log4j: Level token is [DEBUG].
log4j: Category root set to DEBUG
log4j: Parsing appender named "countingconsole".
log4j: Parsing layout options for "countingconsole".
log4j: Setting property [conversionPattern] to [[%-d{yyyy-MM-dd HH:mm:ss}]-[%t-%5p]-[%C-%M(%L)]: %m%n ].
log4j: End of parsing for "countingconsole".
log4j: Setting property [limit] to [3].
log4j: Parsed "countingconsole" options.
log4j: Finished configuring.
[2016-02-25 23:42:16]-[main-DEBUG]-[org.linkinpark.commons.logtest.Log4jTest-logTest(19)]: debug级别的日志输出[2016-02-25 23:42:16]-[main-DEBUG]-[org.linkinpark.commons.logtest.Log4jTest-logTest(20)]: debug级别的日志输出1[2016-02-25 23:42:16]-[main-DEBUG]-[org.linkinpark.commons.logtest.Log4jTest-logTest(21)]: debug级别的日志输出2log4j:ERROR 输出次数[3]达到了[countingconsole]的上限。

  • 关于例子的解释
1,在扩展这个appender的时候,我有参照consoleAppender的实现。核心就是说实现append方法,当然我们直接继承自AppenderSkeleton类来进行的扩展,所以可以直接拿到里面的一些属性,比如layput,比如erroHandler等等
2,刚开始的写这个类的时候,我直接定义了一个limit属性,用来控制日志输出次数,直接是在代码中赋的初始值,为了方便,所以我就想写进配置文件中,但是怎么都注入不进去,控制台一直报下面这个error:
log4j:WARN Failed to set property [limit] to value "3". 

没办法,我只要打开log4j本身的日志,配置文件中设值log4j.debug=true就OK。后来终于发现我的set方法有问题,这个方法这里必须是void返回类型的,而我一般的set方法都是返回自身this,所以这里没有注入。关于log4j处理set注入我下面一节会整理到。

3,当然我们在扩展的时候直接继承ConsoleAppender自这个类也是可以的,这样子的话只需要重写append方法就够了,其他的都不需要了。我自己试了一下测试通过,代码类似,这里不做赘述了。
  • 关于反射set值的另一种方式
我们经常编码,但是其实写反射的代码并不是很多,一般的在IOC框架中都是读取配置文件或者说扫描注解来获取相关key-value,返回跑下set方法的反射,就可以设值到一个对象里面去了,这样子的话就可以把一些属性的设值放入到配置文件中,实现解耦。
在以前我们是这样子编码的:
// 取出需要设置Field值的目标对象Object target = getObject(objAndProp[0]);// 该Field对应的setter方法名:set + "属性的首字母大写" + 剩下部分String mtdName = "set" + objAndProp[1].substring(0 , 1).toUpperCase() + objAndProp[1].substring(1);// 通过target的getClass()获取它实现类所对应的Class对象Class<?> targetClass = target.getClass();// 获取该属性对应的setter方法,下面这一行道出了springIOC的精髓,为什么实现XML我们每次都要提供get和set方法,除了注解的哦Method mtd = targetClass.getMethod(mtdName , String.class);// 通过Method的invoke方法执行setter方法,将config.getProperty(name)的属性值作为调用setter的方法的实参mtd.invoke(target , config.getProperty(name));

看过了log4j的源码以后,我们多了一种选择,就是使用JDK自带的PropertyDescriptor类,这个类就是按照javabean规范写的一个存储器。

该类里面有2个方法可以直接获取我们的get和set方法:setReadMethod,getWriteMethod。以后这也是一种尝试,必要的时候可以参照log4j来用这个方式跑反射。OK,我这里贴出log4j中该类的源码:
package org.apache.log4j.config;import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.Priority;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.spi.OptionHandler;
import org.apache.log4j.spi.ErrorHandler;import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.InterruptedIOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Properties;/*** General purpose Object property setter. Clients repeatedly invokes* {@link #setProperty setProperty(name,value)} in order to invoke setters* on the Object specified in the constructor. This class relies on the* JavaBeans {@link Introspector} to analyze the given Object Class using* reflection.* * <p>* Usage:* * <pre>* PropertySetter ps = new PropertySetter(anObject);* ps.set("name", "Joe");* ps.set("age", "32");* ps.set("isMale", "true");* </pre>* * will cause the invocations anObject.setName("Joe"), anObject.setAge(32),* and setMale(true) if such methods exist with those signatures.* Otherwise an {@link IntrospectionException} are thrown.* * @author Anders Kristensen* @since 1.1*/
public class PropertySetter
{protected Object obj;protected PropertyDescriptor[] props;/*** Create a new PropertySetter for the specified Object. This is done* in prepartion for invoking {@link #setProperty} one or more times.* * @param obj*            the object for which to set properties*/public PropertySetter(Object obj){this.obj = obj;}/*** Uses JavaBeans {@link Introspector} to computer setters of object to be* configured.*/protected void introspect(){try{BeanInfo bi = Introspector.getBeanInfo(obj.getClass());props = bi.getPropertyDescriptors();}catch (IntrospectionException ex){LogLog.error("Failed to introspect " + obj + ": " + ex.getMessage());props = new PropertyDescriptor[0];}}/*** Set the properties of an object passed as a parameter in one* go. The <code>properties</code> are parsed relative to a* <code>prefix</code>.* * @param obj*            The object to configure.* @param properties*            A java.util.Properties containing keys and values.* @param prefix*            Only keys having the specified prefix will be set.*/public static void setProperties(Object obj, Properties properties, String prefix){new PropertySetter(obj).setProperties(properties, prefix);}/*** Set the properites for the object that match the* <code>prefix</code> passed as parameter.* * */public void setProperties(Properties properties, String prefix){int len = prefix.length();for (Enumeration e = properties.propertyNames(); e.hasMoreElements();){String key = (String) e.nextElement();// handle only properties that start with the desired frefix.if (key.startsWith(prefix)){// ignore key if it contains dots after the prefixif (key.indexOf('.', len + 1) > 0){// System.err.println("----------Ignoring---["+key// +"], prefix=["+prefix+"].");continue;}String value = OptionConverter.findAndSubst(key, properties);key = key.substring(len);if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender){continue;}//// if the property type is an OptionHandler// (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender)PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key));if (prop != null && OptionHandler.class.isAssignableFrom(prop.getPropertyType()) && prop.getWriteMethod() != null){OptionHandler opt = (OptionHandler) OptionConverter.instantiateByKey(properties, prefix + key, prop.getPropertyType(), null);PropertySetter setter = new PropertySetter(opt);setter.setProperties(properties, prefix + key + ".");try{Method writeMethod = prop.getWriteMethod();System.out.println("woqu=" + writeMethod);prop.getWriteMethod().invoke(this.obj, new Object[] { opt });}catch (IllegalAccessException ex){LogLog.warn("Failed to set property [" + key + "] to value \"" + value + "\". ", ex);}catch (InvocationTargetException ex){if (ex.getTargetException() instanceof InterruptedException || ex.getTargetException() instanceof InterruptedIOException){Thread.currentThread().interrupt();}LogLog.warn("Failed to set property [" + key + "] to value \"" + value + "\". ", ex);}catch (RuntimeException ex){LogLog.warn("Failed to set property [" + key + "] to value \"" + value + "\". ", ex);}continue;}setProperty(key, value);}}activate();}/*** Set a property on this PropertySetter's Object. If successful, this* method will invoke a setter method on the underlying Object. The* setter is the one for the specified property name and the value is* determined partly from the setter argument type and partly from the* value specified in the call to this method.* * <p>* If the setter expects a String no conversion is necessary.* If it expects an int, then an attempt is made to convert 'value'* to an int using new Integer(value). If the setter expects a boolean,* the conversion is by new Boolean(value).* * @param name*            name of the property* @param value*            String value of the property*/public void setProperty(String name, String value){if (value == null){return;}name = Introspector.decapitalize(name);PropertyDescriptor prop = getPropertyDescriptor(name);// LogLog.debug("---------Key: "+name+", type="+prop.getPropertyType());if (prop == null){LogLog.warn("No such property [" + name + "] in " + obj.getClass().getName() + ".");}else{try{setProperty(prop, name, value);}catch (PropertySetterException ex){LogLog.warn("Failed to set property [" + name + "] to value \"" + value + "\". ", ex.rootCause);}}}/*** Set the named property given a {@link PropertyDescriptor}.* * @param prop*            A PropertyDescriptor describing the characteristics*            of the property to set.* @param name*            The named of the property to set.* @param value*            The value of the property.*/public void setProperty(PropertyDescriptor prop, String name, String value) throws PropertySetterException{Method setter = prop.getWriteMethod();if (setter == null){throw new PropertySetterException("No setter for property [" + name + "].");}Class[] paramTypes = setter.getParameterTypes();if (paramTypes.length != 1){throw new PropertySetterException("#params for setter != 1");}Object arg;try{arg = convertArg(value, paramTypes[0]);}catch (Throwable t){throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed. Reason: " + t);}if (arg == null){throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed.");}LogLog.debug("Setting property [" + name + "] to [" + arg + "].");try{setter.invoke(obj, new Object[] { arg });}catch (IllegalAccessException ex){throw new PropertySetterException(ex);}catch (InvocationTargetException ex){if (ex.getTargetException() instanceof InterruptedException || ex.getTargetException() instanceof InterruptedIOException){Thread.currentThread().interrupt();}throw new PropertySetterException(ex);}catch (RuntimeException ex){throw new PropertySetterException(ex);}}/*** Convert <code>val</code> a String parameter to an object of a* given type.*/protected Object convertArg(String val, Class type){if (val == null){return null;}String v = val.trim();if (String.class.isAssignableFrom(type)){return val;}else if (Integer.TYPE.isAssignableFrom(type)){return new Integer(v);}else if (Long.TYPE.isAssignableFrom(type)){return new Long(v);}else if (Boolean.TYPE.isAssignableFrom(type)){if ("true".equalsIgnoreCase(v)){return Boolean.TRUE;}else if ("false".equalsIgnoreCase(v)){return Boolean.FALSE;}}else if (Priority.class.isAssignableFrom(type)){return OptionConverter.toLevel(v, Level.DEBUG);}else if (ErrorHandler.class.isAssignableFrom(type)){return OptionConverter.instantiateByClassName(v, ErrorHandler.class, null);}return null;}protected PropertyDescriptor getPropertyDescriptor(String name){if (props == null){introspect();}for (int i = 0; i < props.length; i++){if (name.equals(props[i].getName())){return props[i];}}return null;}public void activate(){if (obj instanceof OptionHandler){((OptionHandler) obj).activateOptions();}}
}

  • 总结
Log4j源码还是写的不错的,特别是一些小巧的设计,比如hashtable的性能提升,比如layout引入了解释器模式等等,都是值得我们借鉴的,在扩展性方面也写的挺好。
如果有必要的我们可以自己重写一个appender来实现我们自己的特定功能,OK,先这样子吧。

转载于:https://www.cnblogs.com/LinkinPark/p/5232837.html

Log4j扩展使用--自定义输出相关推荐

  1. log4j日志改json格式自定义输出内容源码及说明

    log4j日志改json格式自定义输出内容源码及说明 最近项目需要接入日志管理平台,要求需要将项目log4j日志格式改为json,没系研究过log4j的我一时间还真被难住了,功夫不负有心人最后还是被我 ...

  2. flume可以实时监控mysql嘛_flume使用(三):实时log4j日志通过flume输出到MySql数据库...

    本文在[flume使用(二):采集远程日志数据到MySql数据库]一文基础之上进行测试操作.本文使用到的: flume版本.jdk版本.mysql.数据库表.javaBean.自定义的mysqlSin ...

  3. 2021年大数据Spark(四十一):SparkStreaming实战案例六 自定义输出 foreachRDD

    目录 SparkStreaming实战案例六 自定义输出-foreachRDD 需求 注意: 代码实现 SparkStreaming实战案例六 自定义输出-foreachRDD 需求 对上述案例的结果 ...

  4. Google Test(GTest)使用方法和源码解析——自定义输出技术的分析和应用

    在介绍自定义输出机制之前,我们先了解下AssertResult类型函数.(转载请指明出于breaksoftware的csdn博客) 在函数中使用AssertionResult AssertionRes ...

  5. log4j 控制台和文件输出乱码问题解决

    来源:http://www.coderli.com/log4j-console-file-garbled 一个小问题,却让我感觉到,现在真正动脑的人很少..我来说说吧. 今天遇到一个小问题,log4j ...

  6. Flink FileSink 自定义输出路径——BucketingSink

    今天看到有小伙伴在问,就想着自己实现一下. 问题: Flink FileSink根据输入数据指定输出位置,比如讲对应日期的数据输出到对应目录 输入数据: 20190716 输出到路径 20190716 ...

  7. c语言自定义输出小数点位数_c语言double类型默认输出小数几位

    C语言中常用的小数有两种类型,分别是 float 或 double:float 称为单精度浮点型,double 称为双精度浮点型.不像整数,小数没有那么多幺蛾子,小数的长度是固定的,float 始终占 ...

  8. vuepress2.0使用教程(8)-扩展MD功能(Section扩展及自定义语法)

    百家饭团队开发的百家饭OpenAPI平台是用vuepress2.0搭建的,搭建的时候不知道2.0还处在beta状态,所以导致后来踩了一些坑,使用过程中vuepress2.0也从2.0.0-beta.1 ...

  9. MapReduce当中自定义输出:多文件输出MultipleOutputs

    自定义输出:多文件输出MultipleOutputs 对于刚才的单独订单topN的问题, 如果需要把单独的订单id的记录放在自己的一个文件中,并以订单id命名.怎么办?multipleOutputs可 ...

最新文章

  1. JavaScript Switch 语句
  2. werkzeug中服务器处理请求的实现
  3. RHCE课程-RH131Linux管理笔记五-Linux远程登陆telnet及ssh服务
  4. 随笔-使用时间管理有感
  5. Sun x4500作为文件服务器的调优
  6. Java Streams,第 2 部分: 使用流执行聚合
  7. 【ABAP】通过ST05分析程序执行路径
  8. 关于linux的服务器搭建,关于搭建linux日志服务器
  9. python文件解除占用_如何使用Python解锁锁定的文件和文件夹(mac)
  10. 网络杂谈, Docker, MongoDB
  11. 【译】nginx关于location部分
  12. C语言extern与static修饰变量
  13. stream常用操作
  14. 10分钟就能学会,Linux操作系统21个shell常用命令
  15. android配置网络权限管理,Android 网络权限配置
  16. 使用BarTender连接Excel打印标签
  17. 你在被窝里刷手机岁月静好,一个名叫 Flink 的 ​“神秘引擎” 却在远方和时间赛跑...
  18. 中国工商注册企业全信息数据
  19. 微信开发工具更新后,跳转页面报错
  20. 计算机网络:BGP路由协议

热门文章

  1. python有什么作用-Python中的闭包到底有什么用
  2. python真的那么火吗-现在为什么 Python 这么火?
  3. python下载安装教程mac-数据分析入门~mac 下载及安装 Python 环境
  4. python3安装pip3-python3及pip3安装
  5. 1_CUDA编程介绍(20181121)
  6. 率土之滨显示未选择服务器是什么意思,率土之滨随机合服方案热门问题解答
  7. django Table doesn't exist
  8. Linux(Ubuntu)下MySQL的安装与配置
  9. ASP.NET MVC过滤器(一)
  10. firefox 自定义快捷键