OK,上面一步我们已经知道了日志框架的必要性,然后我们也对比了直接不用日志框架来记录日志的种种弊端。现在我们开始就来一步一步的实现自己的日志框架。
大体的思路如下:
1,实现多种日志级别,通过设值不同的日志级别来控制项目中日志的输出等级,所以这里就要写一个等级的枚举,这个枚举就定义LinkinLogLevel好了。
2,开始写自己的日志核心类,定义LinkinLog4j类。然后这里要初始化日志对象,然后提供一系列的输出日志的方法,比如info(),debug()等。
3,要控制等级,比如调用info()方法,那么就应该按照约定来输出INFO级别以上的日志,自动屏蔽掉INFO等级之下的输出。
4,每个输出日志的方法最终都要调用一个输出日志的方法,定义log()方法,该方法将一系列的日志内容(类名,等级,日志信息等)输出到控制台上。
OK,大致的思路有了,我们现在开始写代码,现在我们写代码。
日志等级定义枚举代码如下:
package test.junit4test; 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;
} public String toString()
{
return status + "";
} /**
* @创建时间: 2016年2月24日
* @相关参数: @return
* @功能描述: 将所有的枚举封装一个Map返回
*/
public static Map<Integer, LinkinLogLevel> getLevelMap()
{
Map<Integer, LinkinLogLevel> levelMap = new HashMap<>(5, 1);
LinkinLogLevel[] values = LinkinLogLevel.values();
for (LinkinLogLevel linkinLogLevel : values)
{
levelMap.put(linkinLogLevel.getStatus(), linkinLogLevel);
}
return levelMap;
} }
日志框架核心类LinkinLog4j的代码如下:
package test.junit4test; import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.Objects; public class LinkinLog4j
{
private static final Map<Integer, LinkinLogLevel> levelMap; static
{
levelMap = LinkinLogLevel.getLevelMap();
} // 定义2个属性,一个是每个日志文件的名称,一个是每个日志文件的等级
private final String name;
private final int level; /***************** 定义一系列构造器 ***********************************/
public LinkinLog4j(Class<?> klass, LinkinLogLevel level)
{
this(klass.getName(), level.getStatus());
} public LinkinLog4j(Class<?> klass)
{
this(klass.getName(), LinkinLogLevel.ALL.getStatus());
} public LinkinLog4j(String name, int level)
{
this.name = name;
this.level = level;
} /***************** 定义一系列输出日志的方法 ***********************************/
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
* @功能描述: 核心日志方法,输出日志内容到控制台
* <p>
* 判断日志类定义的日志级别,控制一些日志方法的执行和不执行
* 依次将日志的信息一步一步的添加到StringBuilder中然后输出
* TODO
* 1,这里最好使用责任链默认,上一步操作保持对下一步操作对象的封装,实现解耦
* 2,重定向输出,现在默认是控制台,将来重写writeLog方法,实现将日志输出到某一个文件下
* </p>
*/
private void log(int level, String message, Throwable cause)
{
if (isLevelEnabled(level))
{
return;
}
StringBuilder builder = new StringBuilder(32);
appendLogName2Log(builder, name);
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(levelMap.get(level).getDesc()).append("]").append(" ");
} /**
* @创建时间: 2016年2月24日
* @相关参数: @param builder
* @相关参数: @param name
* @功能描述: 追加日志名字
*/
private void appendLogName2Log(StringBuilder builder, String name)
{
builder.append("[").append(name).append("]-");
} /**
* @创建时间: 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 void writeLog(StringBuilder str)
{
System.out.println(str.toString());
} }
OK,现在代码写完了,我们自己写2个测试类来测试下我们的日志框架有没问题。
1,现在我们测试正常输出日志情况,LinkinLog4jTest代码如下:
package test.junit4test; import org.junit.Test; public class LinkinLog4jTest
{ LinkinLog4j log = new LinkinLog4j(LinkinLog4jTest.class, LinkinLogLevel.INFO); @Test
public void testLog()
{
log.debug("debug()。。。");
log.info("info()。。。");
log.error("error()。。。");
} }
看下junit控制台输出:
看下eclipse控制台输出:
[test.junit4test.LinkinLog4jTest]-[INFO] info()。。。
[test.junit4test.LinkinLog4jTest]-[ERROR] error()。。。
OK,上面我配置了LinkinLog4jTest的日志级别是INFO级别,所以自动忽略掉了debug()方法的日志输出。当然,我在初始化日志的时候也可以不传入日志等级,这样子的话就默认最低的等级,也就是输出所有的日志。
2,现在我们测试代码异常然后输出日志的情况。LinkinLog4jTest1代码如下:
package test.junit4test; import org.junit.Test; public class LinkinLog4jTest1
{ LinkinLog4j log = new LinkinLog4j(LinkinLog4jTest1.class); @Test
public void testLog()
{
try
{
throw new NullPointerException("测试异常日志空指针了呢");
}
catch (Exception e)
{
log.debug("testLog()方法抛出异常:" + e.getMessage());
}
log.debug("debug()。。。");
log.info("info()。。。");
log.error("error()。。。");
} }
junit绿条,然后控制台输出如下:
[test.junit4test.LinkinLog4jTest1]-[DEBUG] testLog()方法抛出异常:测试异常日志空指针了呢
[test.junit4test.LinkinLog4jTest1]-[DEBUG] debug()。。。
[test.junit4test.LinkinLog4jTest1]-[INFO] info()。。。
[test.junit4test.LinkinLog4jTest1]-[ERROR] error()。。。
OK,大致的日志框架我们都已经写完了,也实现了基本的功能,但是还是有好多的问题的。
1,这里我们初始化我们的每个日志类的时候就都要输出日志级别,如果不输出的话就默认最低,如何才能将初始化日志类的等级配置和代码解耦放到一个统一的地方来配置呢?使用XML统一配置就OK,也就是新增我们的日志配置文件来统一来配置和初始化我们的日志框架,给用户提供默认配置。
2,现在的框架我们都是打印日志到控制台,暂时还不支持打印日志到文件。
3,打印日志的一系列方法,最终调用同一个输出日志的方法,没有实现人为控制日志输出的功能。比如用户在打印日志的过程中,有些日志要输出这种样式,有些日志要输出那种样式,我们现在的框架暂时还不支持。
虽然日志功能在应用程序开发中是一个非常重要的部件,有些时候日志信息的好坏可以直接影响程序开发的进度。然而日志本身不涉及到任何业务逻辑,因而需要尽量减少它的侵入性,也就说它提供的接口应该尽量的简单。
为了实现接口的简单性,其中一种方法就是使用配置文件记录LinkinLog4j的配置信息,LinkinLog4j则根据配置信息初始化每一个LinkinLog4j实例。这些配置信息包括是否显示日志名称、时间信息;如果显示日志打印时间,其格式如何;默认的日志级别是什么;支持单独配置一些日志名称的日志级别;如果将日志打印到日志文件,则日志文件的名称和目录在哪里等信息。下一篇博客我将实现这里说的这些功能。