本身寫的日誌框架--linkinLog4j--框架可配置+提性能


OK,上一篇博客咱們已經實現了日誌框架的基本的功能,可是還有一個最大的問題就是日誌輸出地不能重定向,而後一些輸出也不可控。那如今咱們來實現一個比較完整的日誌框架。html


設計思路以下:java

1,定義一堆常量LinkinLog4jConstants,這些常量用於框架中日誌輸出的配置項,爲了簡單和方便,這裏不必定非要依賴配置文件,因此在這些常量裏面都要付初始值。設計模式

2,新增配置文件,LinkinLog4j.propertites。這些配置文件中能夠配置上面常量項的任何一項用來覆蓋框架默認。app

3,框架核心類,LinkinLog4j新增shortName,ThreadName屬性,用於日誌輸出類名簡稱,線程名字。框架

4,LinkinLog4j定義幾個新的靜態常量,在static塊中統一初始化。比較重要的是:要有一個輸出流,用來重定向Appender,要有一個日誌+等級映射的map,用來控制各類子類的等級jvm

5,重寫log(),按照配置文件中的各類配置來控制日誌某些內容的輸出與不輸出。ide

6,框架輔助類,LinkinLog4jHelper。框架日誌工廠類,LinkinLog4jFactory,用來生成單例LinkinLog4j。工具


OK,如今我先貼出代碼,而後一邊本身寫個測試來試一下這個框架看下效果。我自測經過。性能

框架核心類,LinkinLog4j:測試

package linkinframe.linkinLog4j;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.TreeMap;

public class LinkinLog4j
{
	private static final String PROP_LOG_PREFIX; // 配置文件key值前綴
	private static boolean SHOW_LOG_NAME; // 是否輸出類名全名旗標
	private static boolean SHOW_SHORT_NAME; // 是否輸出類名簡稱旗標
	private static boolean SHOW_THREAD_NAME; // 是否輸出線程名旗標
	private static boolean SHOW_DATE_INFO; // 是否輸出時間旗標
	private static boolean FLUSH; // 是否刷新輸出流旗標
	private static final Map<String, LinkinLogLevel> LEVELDESCMAP; // 全部等級枚舉 key值數字
	private static final Map<Integer, LinkinLogLevel> LEVELSTATUSMAP; // 全部等級枚舉 key值英語
	private static Map<String, Integer> CONFIGLEVELS; // 封裝配置文件中的每一個類的日誌級別
	private static SimpleDateFormat DATEFORMAT; // 時間格式化器
	private static int ROOT_LEVEL; // 默認等級,若是沒有配置就使用該等級
	private static PrintStream OUT; // 輸出流

	static
	{
		LEVELDESCMAP = LinkinLogLevel.getLevelDescMap();
		LEVELSTATUSMAP = LinkinLogLevel.getLevelStatusMap();
		InputStream in = LinkinLog4jHelper.getConfigInputStream(LinkinLog4jConstants.PROP_NAME);
		Properties props = new Properties();
		if (in != null)
		{
			try
			{
				props.load(in);
				in.close();
			}
			catch (IOException e)
			{
			}
		}
		// 添加系統的全部的常量
		props.putAll(System.getProperties());
		PROP_LOG_PREFIX = LinkinLog4jConstants.PROP_LOG_PREFIX;
		SHOW_LOG_NAME = LinkinLog4jHelper.getBooleanProperty(props, LinkinLog4jConstants.SHOW_LOG_NAME, LinkinLog4jConstants.DEFAULT_SHOW_LOG_NAME);
		SHOW_SHORT_NAME = LinkinLog4jHelper.getBooleanProperty(props, LinkinLog4jConstants.SHOW_SHORT_NAME, LinkinLog4jConstants.DEFAULT_SHOW_SHORT_NAME);
		SHOW_THREAD_NAME = LinkinLog4jHelper.getBooleanProperty(props, LinkinLog4jConstants.SHOW_THREAD_NAME, LinkinLog4jConstants.DEFAULT_SHOW_THREAD_NAME);
		SHOW_DATE_INFO = LinkinLog4jHelper.getBooleanProperty(props, LinkinLog4jConstants.SHOW_DATE_INFO, LinkinLog4jConstants.DEFAULT_SHOW_DATE_INFO);
		FLUSH = LinkinLog4jHelper.getBooleanProperty(props, LinkinLog4jConstants.FLUSH, LinkinLog4jConstants.DEFAULT_FLUSH);
		ROOT_LEVEL = toIntegerLevel(LinkinLog4jHelper.getProperty(props, LinkinLog4jConstants.ROOT_LEVEL, LinkinLog4jConstants.DEFAULT_LEVEL));
		// 初始化配置文件中的全部的日誌文件+等級
		CONFIGLEVELS = parseConfigLevels(props);
		// 初始化時間格式。
		String dateFormatStr = LinkinLog4jHelper.getProperty(props, LinkinLog4jConstants.DATE_FORMAT, LinkinLog4jConstants.DEFAULT_DATA_FORMAT);
		DATEFORMAT = new SimpleDateFormat(dateFormatStr);
		// 初始化輸出流,若是沒有找見則重定向爲控制檯輸出
		String logFile = LinkinLog4jHelper.getProperty(props, LinkinLog4jConstants.LOG_FILE, LinkinLog4jConstants.DEFAULT_LOG_FILE);
		boolean append = LinkinLog4jHelper.getBooleanProperty(props, LinkinLog4jConstants.LOG_FILE_APPEND, LinkinLog4jConstants.DEFAULT_LOG_FILE_APPEND);
		OUT = getPrintStream(logFile, append);

		// jvm中增長一個關閉的鉤子
		Runtime runtime = Runtime.getRuntime();
		runtime.addShutdownHook(new Thread()
		{
			@Override
			public void run()
			{
				try
				{
					shutdown();
				}
				catch (Exception e)
				{
					System.err.println("框架shutdown出錯!!!");
					e.printStackTrace(System.err);
				}
			}

			private void shutdown()
			{
			}

		});
	}

	private final String name; // 每一個日誌文件的名稱
	private String shortName; // 日誌文件簡稱
	private String threadName; // 線程名字
	private final int level; // 每一個日誌文件的等級

	public LinkinLog4j(String name)
	{
		this.name = name;
		this.level = getLogLevel(name);
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param props
	 * @相關參數: @return
	 * @功能描述: 封裝日誌名+日誌等級進一個map
	 */
	private static Map<String, Integer> parseConfigLevels(Properties props)
	{
		Map<String, Integer> map = new TreeMap<String, Integer>();
		for (String key : props.stringPropertyNames())
		{
			if (key != null && key.startsWith(PROP_LOG_PREFIX))
			{
				String logLevelValue = props.getProperty(key);
				String logName = parseLogName(key);
				map.put(logName, toIntegerLevel(logLevelValue));
			}
		}
		return map;
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param logNameKey
	 * @相關參數: @return
	 * @功能描述: 獲取日誌名稱,配置文件中去掉配置前綴
	 */
	private static String parseLogName(String logNameKey)
	{
		return logNameKey.substring(PROP_LOG_PREFIX.length());
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param property
	 * @相關參數: @return
	 * @功能描述: 經過desc獲取等級枚舉中的status
	 */
	private static int toIntegerLevel(String desc)
	{
		return LEVELDESCMAP.get(desc).getStatus();
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param logFile
	 * @相關參數: @param append
	 * @相關參數: @return
	 * @功能描述: 獲取輸出地appender
	 */
	private static PrintStream getPrintStream(String logFile, boolean append)
	{
		PrintStream out = null;
		try
		{
			LinkinLog4jHelper.createNewFileIfNotExists(logFile);
			out = new PrintStream(new FileOutputStream(logFile, append));
		}
		catch (Exception e)
		{
			System.err.println("未找到輸出日誌路徑,默認使用控制檯輸出!");
			return System.out;
		}
		return out;
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param logName
	 * @相關參數: @return
	 * @功能描述: 獲取日誌類的輸出日誌等級
	 */
	private int getLogLevel(String logName)
	{
		// 一、若是沒有配置,使用框架默認等級
		if (CONFIGLEVELS == null || CONFIGLEVELS.isEmpty())
		{
			return ROOT_LEVEL;
		}
		// 二、若是配置了,使用配置文件中的等級
		int logLevel = -1;
		// {level=10000, level.test.junit4test=2147483647}
		// test.junit4test.LinkinLog4jTest
		for (String name : CONFIGLEVELS.keySet())
		{
			if (logName.startsWith(name))
			{
				logLevel = CONFIGLEVELS.get(name);
			}
		}
		if (logLevel == -1)
		{
			logLevel = ROOT_LEVEL;
		}
		return logLevel;
	}

	/***************** 定義一系列輸出日誌的方法 ***********************************/
	public void info(String message)
	{
		info(message, null);
	}

	public void info(String message, Throwable cause)
	{
		log(LinkinLogLevel.INFO.getStatus(), message, cause);
	}

	public void debug(String message)
	{
		debug(message, null);
	}

	public void debug(Throwable cause)
	{
		debug(null, cause);
	}

	public void debug(String message, Throwable cause)
	{
		log(LinkinLogLevel.DEBUG.getStatus(), message, cause);
	}

	public void error(String message)
	{
		error(message, null);
	}

	public void error(String message, Throwable cause)
	{
		log(LinkinLogLevel.ERROR.getStatus(), message, cause);
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param level
	 * @相關參數: @param message
	 * @相關參數: @param cause
	 * @功能描述: 核心日誌方法,輸出日誌內容到appender
	 * <p>
	 * 判斷日誌類定義的日誌級別,控制一些日誌方法的執行和不執行
	 * 依次將日誌的信息一步一步的添加到StringBuilder中而後輸出
	 * </p>
	 */
	private void log(int level, String message, Throwable cause)
	{
		if (isLevelEnabled(level))
		{
			return;
		}
		StringBuilder builder = new StringBuilder(128);
		appendDateInfo2Log(builder);
		appendLogName2Log(builder, name);
		appendThreadName2Log(builder);
		appendLevel2Log(builder, level);
		appendMessqge2Log(builder, message);
		appendCauseInfo2Log(builder, cause);
		writeLog(builder);
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param level 日誌類中調用的各類輸出日誌方法的等級
	 * @相關參數: @return true:忽略該輸出日誌方法,false:執行該輸出日誌方法
	 * @功能描述: 控制一些日誌的輸出仍是忽略
	 * <p>
	 * 日誌類本身配置的日誌等級VS日誌類中調用的各類輸出日誌方法的等級
	 * </p>
	 */
	private boolean isLevelEnabled(int level)
	{
		if (level < this.level)
		{
			return true;
		}
		return false;
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param builder
	 * @相關參數: @param level
	 * @功能描述: 追加日誌等級
	 */
	private void appendLevel2Log(StringBuilder builder, int level)
	{
		builder.append("[").append(LEVELSTATUSMAP.get(level).getDesc()).append("]").append(" ");
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param builder
	 * @功能描述: 追加時間信息,取值當前時間
	 * 注意:這裏要加鎖
	 */
	private void appendDateInfo2Log(StringBuilder builder)
	{
		if (SHOW_DATE_INFO)
		{
			Date date = new Date();
			String dateStr = "";
			synchronized (DATEFORMAT)
			{
				dateStr = DATEFORMAT.format(date);
			}
			builder.append(dateStr).append(LinkinLog4jConstants.LOG_SEPARATOR);
		}
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param builder
	 * @相關參數: @param name
	 * @功能描述: 追加日誌名字
	 */
	private void appendLogName2Log(StringBuilder builder, String name)
	{
		if (SHOW_SHORT_NAME)
		{
			builder.append(getShortName(name));
		}
		else if (SHOW_LOG_NAME)
		{
			builder.append(name);
		}
		builder.append(LinkinLog4jConstants.LOG_SEPARATOR);
	}

	private void appendThreadName2Log(StringBuilder builder)
	{
		if (SHOW_THREAD_NAME)
		{
			builder.append(getThreadName());
		}
		builder.append(LinkinLog4jConstants.LOG_SEPARATOR);
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param name
	 * @相關參數: @return
	 * @功能描述: 根據類名獲取簡稱
	 * <p>
	 * linkin.package.className→className
	 * </p>
	 */
	protected Object getShortName(String name)
	{
		if (shortName == null)
		{
			if (name == null)
			{
				shortName = "null";
				return shortName;
			}
			int idx = name.lastIndexOf(".");
			if (idx < 0)
			{
				shortName = name;
			}
			else
			{
				shortName = name.substring(idx + 1);
			}
		}
		return shortName;
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param builder
	 * @相關參數: @param message
	 * @功能描述: 追加日誌內容信息
	 */
	private void appendMessqge2Log(StringBuilder builder, String message)
	{
		builder.append(message);
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param builder
	 * @相關參數: @param cause
	 * @功能描述: 追加日誌異常
	 */
	private void appendCauseInfo2Log(StringBuilder builder, Throwable cause)
	{
		if (Objects.nonNull(cause))
		{
			builder.append("<");
			builder.append(cause.getMessage());
			builder.append(">");
			builder.append(System.getProperty("line.separator"));
			StringWriter writer = new StringWriter();
			PrintWriter printer = new PrintWriter(writer);
			cause.printStackTrace(printer);
			printer.close();
			builder.append(writer.toString());
		}
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @param str 全部的日誌輸出的內容
	 * @功能描述: 輸出日誌
	 * 注意:要加鎖,別打印一半被打斷
	 */
	public synchronized void writeLog(StringBuilder str)
	{
		OUT.println(str.toString());
		if (FLUSH)
		{
			OUT.flush();
		}
	}

	/**
	 * @建立時間: 2016年2月24日
	 * @相關參數: @return
	 * @功能描述: 獲取當前線程
	 */
	public synchronized String getThreadName()
	{
		if (threadName == null)
		{
			threadName = Thread.currentThread().getName();
		}
		return threadName;
	}

}

框架常量類,LinkinLog4jConstants:

package linkinframe.linkinLog4j;

/**
 * @建立做者: LinkinPark
 * @建立時間: 2016年2月24日
 * @功能描述: 框架的默認值,配置文件中相關配置覆蓋這份默認值
 */
public class LinkinLog4jConstants
{
	// 配置文件名稱
	public static final String PROP_NAME = "linkinLog4j.properties";
	// 配置文件key值前綴
	public static final String PROP_PREFIX = "linkinLog4j.";
	// 配置日誌文件名字+等級的映射
	public static final String PROP_LOG_PREFIX = PROP_PREFIX + "log.level.";
	// 默認時間格式化器
	public static final String DATE_FORMAT = PROP_PREFIX + "dateFormat";
	public static final String DEFAULT_DATA_FORMAT = "HH:mm:ss";
	// 默認日誌級別
	public static final String ROOT_LEVEL = PROP_PREFIX + "root.level";
	public static final String DEFAULT_LEVEL = LinkinLogLevel.ALL.getDesc();
	// 默認顯示日誌日誌所在類名簡稱
	public static final String SHOW_LOG_NAME = PROP_PREFIX + "showLogName";
	public static boolean DEFAULT_SHOW_LOG_NAME = true;
	// 默認顯示線程
	public static final String SHOW_THREAD_NAME = PROP_PREFIX + "showThreadName";
	public static boolean DEFAULT_SHOW_THREAD_NAME = true;
	// 默認不顯示日誌所在類名全稱
	public static final String SHOW_SHORT_NAME = PROP_PREFIX + "showShortName";
	public static boolean DEFAULT_SHOW_SHORT_NAME = false;
	// 默認不顯示時間
	public static final String SHOW_DATE_INFO = PROP_PREFIX + "showDateInfo";
	public static boolean DEFAULT_SHOW_DATE_INFO = true;
	// 重定向日誌輸出路徑
	public static final String LOG_FILE = PROP_PREFIX + "logFile";
	public static final String DEFAULT_LOG_FILE = null;
	// 默認日誌刷緩衝
	public static final String FLUSH = PROP_PREFIX + "logFile.flush";
	public static boolean DEFAULT_FLUSH = true;
	// 是否追加日誌到一個文件尾部
	public static final String LOG_FILE_APPEND = PROP_PREFIX + "logFile.append";
	public static final boolean DEFAULT_LOG_FILE_APPEND = true;
	// 日誌的分割符
	public static final String LOG_SEPARATOR = "-";

}

框架日誌等級枚舉,LinkinLogLevel:

package linkinframe.linkinLog4j;

import java.util.HashMap;
import java.util.Map;

/**
 * @建立做者: LinkinPark
 * @建立時間: 2016年2月23日
 * @功能描述: 日誌等級枚舉。
 * <p>
 * Log4J中的全部的等級以下:all→trace→debug→info→warn→error→fatal→off
 * 這裏本身模擬的等級以下:all→debug→info→error→off
 * </p>
 */
public enum LinkinLogLevel
{
	ALL(Integer.MIN_VALUE, "ALL"), DEBUG(10000, "DEBUG"), INFO(20000, "INFO"), ERROR(30000, "ERROR"),
	OFF(Integer.MAX_VALUE, "OFF");

	private final int status;
	private final String desc;

	private LinkinLogLevel(int status, String desc)
	{
		this.status = status;
		this.desc = desc;
	}

	public int getStatus()
	{
		return status;
	}

	public String getDesc()
	{
		return desc;
	}

	/*************** 提供2個map,分別封裝全部的枚舉 ***************/
	public static Map<String, LinkinLogLevel> getLevelDescMap()
	{
		Map<String, LinkinLogLevel> levelMap = new HashMap<>(5, 1);
		LinkinLogLevel[] values = LinkinLogLevel.values();
		for (LinkinLogLevel linkinLogLevel : values)
		{
			levelMap.put(linkinLogLevel.getDesc(), linkinLogLevel);
		}
		return levelMap;
	}

	public static Map<Integer, LinkinLogLevel> getLevelStatusMap()
	{
		Map<Integer, LinkinLogLevel> levelMap = new HashMap<>(5, 1);
		LinkinLogLevel[] values = LinkinLogLevel.values();
		for (LinkinLogLevel linkinLogLevel : values)
		{
			levelMap.put(linkinLogLevel.getStatus(), linkinLogLevel);
		}
		return levelMap;
	}

}

框架幫助類,LinkinLog4jHelper類:

package linkinframe.linkinLog4j;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * @建立做者: LinkinPark
 * @建立時間: 2016年2月24日
 * @功能描述: 工具類
 */
public class LinkinLog4jHelper
{

	public static String getProperty(Properties props, String key)
	{
		return getProperty(props, key, "");
	}

	public static String getProperty(Properties props, String key, String defaultValue)
	{
		return props.getProperty(key, defaultValue);
	}

	public static boolean getBooleanProperty(Properties props, String key)
	{
		return getBooleanProperty(props, key, false);
	}

	public static boolean getBooleanProperty(Properties props, String key, boolean defaultValue)
	{
		String property = props.getProperty(key);
		if (property == null || property == "" || property == "null")
		{
			return defaultValue;
		}
		return new Boolean(property).booleanValue();
	}

	public static InputStream getConfigInputStream(String configName)
	{
		ClassLoader classLoader = getContextClassLoader();
		InputStream in = classLoader.getResourceAsStream(configName);
		if (in == null)
		{
			in = LinkinLog4j.class.getClassLoader().getResourceAsStream(configName);
		}
		if (in == null)
		{
			in = LinkinLog4j.class.getResourceAsStream(configName);
		}
		return in;
	}

	public static ClassLoader getContextClassLoader()
	{
		return Thread.currentThread().getContextClassLoader();
	}

	/**
	 * @建立時間: 2016年2月25日
	 * @相關參數: @param logFile
	 * @功能描述: 文件不存在,則建立一個
	 */
	public static void createNewFileIfNotExists(String logFile)
	{
		File file = new File(logFile);
		File file1 = new File(file.getParent());
		if (!file.exists())
		{
			file1.mkdirs();
			try
			{
				file.createNewFile();
			}
			catch (IOException e)
			{
				System.err.println("建立日誌出錯!!!");
			}
		}
	}

}

框架工廠類,LinkinLog4jFactory:

package linkinframe.linkinLog4j;

import java.util.Hashtable;

public class LinkinLog4jFactory
{
	// 字符串包裝key值,加快hashTable查找效率
	static Hashtable<CategoryKey, Object> hashTable = new Hashtable<>(10, 1);

	public static LinkinLog4j getLogger(Class<?> klass)
	{
		return getLogger(klass.getName());
	}

	public static LinkinLog4j getLogger(String name)
	{
		CategoryKey key = new CategoryKey(name);
		LinkinLog4j logger;
		synchronized (hashTable)
		{
			Object o = hashTable.get(key);
			if (o == null)
			{
				logger = makeNewLoggerInstance(name);
				hashTable.put(key, logger);
				return logger;
			}
			else if (o instanceof LinkinLog4j)
			{
				return (LinkinLog4j) o;
			}
			else
			{
				return null;
			}
		}
	}

	public static LinkinLog4j makeNewLoggerInstance(String name)
	{
		return new LinkinLog4j(name);
	}

}

還有一個key值封裝類,該類用來封裝一個字符串加一個hashCode值的緩衝,而後重寫hashCode方法,加快工廠效率。

package linkinframe.linkinLog4j;

/**
 * @建立做者: LinkinPark
 * @建立時間: 2016年2月23日
 * @功能描述: 字符串包裝,加快hashTable查找效率
 */
public class CategoryKey
{
	String name;
	int hashCache;

	CategoryKey(String name)
	{
		this.name = name;
		hashCache = name.hashCode();
	}

	final public int hashCode()
	{
		return hashCache;
	}

	final public boolean equals(Object rArg)
	{
		if (this == rArg)
		{
			return true;
		}

		if (rArg != null && CategoryKey.class == rArg.getClass())
		{
			return name.equals(((CategoryKey) rArg).name);
		}
		else
		{
			return false;
		}
	}
}

OK,上面的代碼寫完了,咱們寫測試以前,先提供一份完整的linkinLog4j.propertites配置文件。

#################################
#該配置文件可省,框架默認向控制檯輸出日誌#
##[ALL→DEBUG→INFO→ERROR→OFF]###
#################################
#是否輸出類的簡稱,只有一個類名。默認false
linkinLog4j.showShortName=false
#是否輸出線程名字,默認true
#linkinLog4j.showThreadName=false
#是否輸出時間信息,取值當前時間,默認true
linkinLog4j.showDateInfo=true
#時間格式化器,默認HH:mm:ss
linkinLog4j.dateFormat=yyyy-MM-dd HH:mm:ss
#配置全局日誌級別,默認ALL
linkinLog4j.root.level=DEBUG
#配置日誌重定向文件appender,默認控制檯
linkinLog4j.logFile=huhu/log/linkinLog4j.log
#是否追加日誌到文件尾部,默認true
linkinLog4j.logFile.append=false
#是否刷新日誌在輸出流中的緩衝,默認true
linkinLog4j.logFile.flush=true
#################################
#支持配置包,配置類,覆蓋root默認,覆蓋包#
#################################
#linkinLog4j.log.level.test.junit4test=DEBUG
linkinLog4j.log.level.test.junit4test.LinkinLog4jTest1=INFO
#################################
#TODO:多個線程在日誌中線程名字同樣,我去
#################################


固然上面的配置文件能夠省略,這一套日誌框架都有賦初始值的,若是沒有配置該配置文件,則使用控制檯作appender,而後按照默認約定格式來輸出默認內容。典型的咱們來寫幾個測試,看下輸出狀況。

1,不用配置文件,直接使用框架默認打印日誌。直接修改常量,使得框架找不到該配置文件就OK。

測試類代碼以下:

package test.junit4test;

import org.junit.Test;

import linkinframe.linkinLog4j.LinkinLog4j;
import linkinframe.linkinLog4j.LinkinLog4jFactory;

public class LinkinLog4jTest
{
	LinkinLog4j log = LinkinLog4jFactory.getLogger(LinkinLog4jTest.class);

	@Test
	public void testLog()
	{
		log.debug("debug()。。。");
		log.info("info()。。。");
		log.error("error()。。。");
	}

}
OK,沒有問題。junit控制檯綠條,而後控制檯輸出結果以下:

未找到輸出日誌路徑,默認使用控制檯輸出!
10:08:41-test.junit4test.LinkinLog4jTest-main-[DEBUG] debug()。。。
10:08:41-test.junit4test.LinkinLog4jTest-main-[INFO] info()。。。
10:08:41-test.junit4test.LinkinLog4jTest-main-[ERROR] error()。。。

2,使用配置文件,設置一些常量來控制日誌的輸出,包括重定向日誌文件,包括輸出內容等等。

OK,沒有問題,junit綠條,而後日誌內容被重定向了咱們制定的路徑文件下。



3,從新設置子類文件的日誌級別,可使用包配置,也可使用類名配置。咱們如今修改linkinLog4j.propertites文件,定義日誌等級:

#################################
#支持配置包,配置類,覆蓋root默認,覆蓋包#
#################################
#linkinLog4j.log.level.test.junit4test=DEBUG
linkinLog4j.log.level.test.junit4test.LinkinLog4jTest=INFO

運行上面的測試咱們打開huhu/log/linkinLog4j.log,日誌內容從新刷新且只輸出了INFO級別以上的日誌:

2016-02-25 10:12:56-test.junit4test.LinkinLog4jTest-main-[INFO] info()。。。
2016-02-25 10:12:56-test.junit4test.LinkinLog4jTest-main-[ERROR] error()。。。



總結:

雖然日誌功能在應用程序開發中是一個很是重要的部件,有些時候日誌信息的好壞能夠直接影響程序開發的進度。

可是日誌自己不涉及到任何業務邏輯,於是須要儘可能減小它的侵入性,也就說它提供的接口應該儘可能的簡單。爲了實現接口的簡單性,其中一種方法就是使用配置文件記錄LinkinLog4j的配置信息,LinkinLog4j則根據配置信息初始化每個日誌核心實例。

這些配置信息包括:

1,是否顯示日誌名稱、時間信息;若是顯示日誌打印時間,其格式如何;

2,默認的日誌級別是什麼,默認的日誌追加方式是什麼,若是沒有配置文件要給與一種約定。

3,支持單獨配置一些日誌名稱的日誌級別;能夠覆蓋包,能夠覆蓋類,實現文件日誌配置繼承。

4,若是將日誌打印到日誌文件,則日誌文件的名稱和目錄在哪裏等信息。


OK,如今日誌框架基本可使用了,功能也比較完善了,這裏核心代碼我有借鑑Log4j源碼,剩下的一些不足就是一些異常捕獲狀況,一些性能上面的提高,一些功能和方法的抽象,好比咱們這裏有輸出地Appender,日誌格式化器Layout。咱們都應該抽象成接口或者抽象類,而後衍生多個子類來嫁入咱們的框架輸出日誌。框架沒有完美,咱們閱讀源碼,最大的用處就是看懂它的設計,來吸取一些好的技巧和設計模式。接下來,我會寫幾篇關於Log4j源碼解析的博客。先這樣吧。

相關文章
相關標籤/搜索