• 写在前面的话

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; /**
* 关闭资源
*/
@Override
public void close()
{
if (this.closed)
{
return;
}
this.closed = true;
} /**
* 这里需要使用格式化器
*/
@Override
public boolean requiresLayout()
{
return true;
} @Override
protected 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); @Test
public 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级别的日志输出2 log4j: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 prefix
if (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,先这样子吧。









Log4j扩展使用--自定义输出的更多相关文章

  1. log4j.xml配置,包含自定义log4j日志级别及输出日志到不同文件

      一.配置 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configura ...

  2. Log4j扩展使用--日志记录器Logger

    OK,现在我们认真的研究下Logger的配置,进行相关配置扩展. Log4j有三个主要的组件:Loggers(记录器),Appenders(输出源)和Layouts(布局).其中,Logger负责记录 ...

  3. 使用log4j配置不同文件输出不同内容

    敲代码中很不注意写日志,虽然明白很重要.今天碰到记录日志,需要根据内容分别输出到不同的文件. 参考几篇文章: 感觉最详细:http://blog.csdn.net/azheng270/article/ ...

  4. C#Winform使用扩展方法自定义富文本框(RichTextBox)字体颜色

    在利用C#开发Winform应用程序的时候,我们有可能使用RichTextBox来实现实时显示应用程序日志的功能,日志又分为:一般消息,警告提示 和错误等类别.为了更好地区分不同类型的日志,我们需要使 ...

  5. 《手把手教你》系列基础篇(八十七)-java+ selenium自动化测试-框架设计基础-Log4j 2实现日志输出-上篇(详解教程)

    1.简介 Apache Log4j 是一个非常古老的日志框架,并且是多年来最受欢迎的日志框架. 它引入了现代日志框架仍在使用的基本概念,如分层日志级别和记录器. 2015 年 8 月 5 日,该项目管 ...

  6. Lrc2srt精灵,增加自定义输出编码

    2015.4.8 对中文支持有点问题,修改了一下,支持自定义输出编码! 修改了建议行末偏移,通常100到200最好了,人的反应时间! http://files.cnblogs.com/files/ro ...

  7. Log4j 输出的日志中时间比系统时间少了8小时的解决方法,log4j日志文件重复输出

    1. 第一个问题:时间少了8小时 Log4j 输出的日志中,时间比系统时间少了8小时,但是 eclipse 控制台输出的日志的时间却是对的. log4j配置如下: #all logger output ...

  8. 使用log4j的时候如何输出printStackTrace()的堆栈信息

    使用log4j的时候如何输出printStackTrace()的堆栈信息 研究了一下发现很简单,如下: log.error(e.getMessage(),e); 输出信息如下: 2009-05-11 ...

  9. EDIUS设置自定义输出的方法

    在做后期视频剪辑时,往往根据需求,需要输出不同分辨率格式的视频文件,那在EDIUS中,如何自定义输出设置,使之符合自己的需要呢?下面小编就来详细讲讲EDIUS自定义输出的一二事吧. 当剪辑完影片,设置 ...

随机推荐

  1. C++\virtual 虚函数、纯虚函数

    前提摘要: 虚函数联系到多态,多态联系到继承.所以本文中都是在继承层次上做文章.没了继承,什么都没得谈. 虚函数定义: 指向基类的指针或引用在操作它的多态类(子类/派生类)对象时,会根据不同的类对象, ...

  2. Unity3d 协程

    参考文章: http://blog.csdn.net/onafioo/article/details/48979939 http://www.cnblogs.com/zhaoqingqing/p/37 ...

  3. react 开发知识准备

    react react使用教程 babel babel 可用于ES6转换为ES5,jsx转换为原生js. ES6 ES6 语法 webpack webpack打包工具,它把不同的.相互依赖的静态资源都 ...

  4. 有关python下二维码识别用法及识别率对比分析

    最近项目中用到二维码图片识别,在python下二维码识别,目前主要有三个模块:zbar .zbarlight.zxing. 1.三个模块的用法: #-*-coding=utf-8-*- import ...

  5. springboot之集成mybatis mongo shiro druid redis jsp

    闲来无事,研究一下spingboot  发现好多地方都不一样了,第一个就是官方默认不支持jsp  于是开始狂找资料  终于让我找到了 首先引入依赖如下: <!-- tomcat的支持.--> ...

  6. Codeforces Round #434 (Div. 2, based on Technocup 2018 Elimination Round 1)&&Codeforces 861B Which floor?【枚举,暴力】

    B. Which floor? time limit per test:1 second memory limit per test:256 megabytes input:standard inpu ...

  7. bzoj:3085: 反质数加强版SAPGAP

    Description 先解释一下SAPGAP=Super AntiPrime, Greatest AntiPrime(真不是网络流),于是你就应该知道本题是一个关于反质数(Antiprime)的问题 ...

  8. 查找第k小的元素(O(n)递归解法)

    今天分享一个小技巧,虽然是小技巧但是还是很有价值的,曾经是微软的面试题.题目是这样的,一个无序的数组让你找出第k小的元素,我当时看到这道题的时候也像很多人一样都是按普通的思维,先排序在去第K个,但是当 ...

  9. Codeforces Round #326 (Div. 2) B

    1.每一个合数都可以由若干个素数相乘而得到 2.质因数知识 :求一个数因数的个数等于它的每个质因数的次数加一的和相乘的积因为质因数可以不用,所以要加一.例如6=2x3,两个质因数都是一次,如果两个质因 ...

  10. 前端自动化-----gulp详细入门(转)

    简介: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器:她不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成:使用她,我们不仅可以很愉快的编写代码 ...