OK,在开始研究Log4j的源码之前,我们先来自己模拟一个日志工具,名字就叫linkinlog4j好了。
在软件开发过程中,出现bug总是在所难免;事实上,以我个人经验,即使在实际开发阶段,fix bug时间要远超过写代码的时间。在开发阶段,比较有效的fix bug的方法当然是调试,然而如果代码比较复杂,而且开始对代码不是很熟悉,那么我们很容易在方法调用之间迷失方向;如果bug出现在多线程环境中,那么很多时候调试就无能为力了;另外当代码部署到服务器上运行时,不管是在UAT测试环境还是Production环境,此时要调试很多时候是不可能。相信有过几年工作经验的码农在和运维,测试交流的时候经常提及的日志级别了。
为了解决这些问题,我们可以在开发过程中事先在一些关键点上打印出一些日志信息(log),以利于在出问题后能知道当时运行的环境信息,特别是在production上,我们可以分析log以确定问题所在,当然前提log信息足够多。不过加入日志信息后,不难想象,它会对程序的运行性能产生一定的影响,而且如果log过多会导致有用的信息不容易找到,如果过少又不具备很大的参考价值,这样的日志除了降低程序的性能,貌似对其他方面没有帮助。
关于性能,Log4J号称在这面做了很多优化,但是据说logback做的更好(logback的源码还没来得及看,只是简单的用过,所以还不是很了解);而关于如何写log、在哪里写log、要把那些信息写入log中,个人感觉这是一门很大的学问,而且也是根据不同项目而不同,我以菜鸟身份在这里也就不多做赘述了。
OK,现在我们先来自己写一个测试类,然后通过System.out打印到控制台。代码如下:
package test.junit4test; import org.junit.Test; public class LinkinLogTest
{ @Test
public void testLog()
{
System.out.println("测试方法开始。。。");
try
{
throw new NullPointerException("这里人工抛出一个异常!");
}
catch (Exception e)
{
System.out.println("人工捕获一个异常" + e.getMessage());
e.printStackTrace(System.out);
}
System.out.println("测试方法结束。。。");
} }
运行下面的代码,控制台输出如下:
测试方法开始。。。
人工捕获一个异常这里人工抛出一个异常!
java.lang.NullPointerException: 这里人工抛出一个异常!
at test.junit4test.LinkinTest.testLog(LinkinTest.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
测试方法结束。。。
OK,没有问题。但是这段代码有这很明显的缺陷:
1,我们这些日志只能打印在控制台。那么如果我们发布了我们的项目到我们的服务器,我们需要查看的是日志文件而不是控制台了。
2,这里我们自己写的这个日志类没有提现等级,如果我想控制不同的级别来控制某些的内容的输出和不输出,即使我们这里加了一个flag旗标,也没有从根本上解决问题。
3,现在我们的日志的内容都是比较简单,如果我们想要在控制台显示我们的日志的类名,方法运行的时间,所在的线程等等这些内容时,就要不停的重复的写一堆代码。
4,很多的时候我们希望我们的日志按照某种约定来生成,比如说按照日期,按照文件的大小存档,这个时候我们上面的那段代码根本实现不了。
OK,问题很多,那我们来一步一步的解决吧。