zoukankan      html  css  js  c++  java
  • Log4j扩展使用--自定义输出


    • 写在前面的话

    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,先这样子吧。




  • 相关阅读:
    Unity3d与Android交互
    A star 寻路
    网络协议
    数据驱动
    有限状态机(FSM)
    自己封装一个Log模块
    Unity5.x版本AssetBundle加载研究
    Unity5.x版本AssetBundle打包研究
    alidoing --使用JS实现多语言框架、喜欢的请进、、瓦特平台!
    使用代码生成器“代码工厂”快速生成B/S程序代码
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232837.html
Copyright © 2011-2022 走看看