log4j

对于开发和调试以及上线运营,日志必不可少,常用日志工具有:Log4j、commons-logging、甚至System.out。

这里用Log4j为例,说说如何将日志输出和业务代码做到最大分离。

Log4j 一般使用方法:

  1.定义配置文件:log4j.properties
  2.在需要输出日志的类里获取Logger实例:static Logger logger = Logger.getLogger(AAA.class);
  3.输出日志:logger.debug("xxx");

大家这么做都已经轻车熟路了,但是这样做有两个问题:

  1.每个需要输出日志信息的类里,都要引入log4j的包,都要定义一个Logger实例。

  2.如果一旦系统由log4j改为别的日志输出方式,如System.out 或commons-logging,则要修改N多个类的代码。

  以上两点,使得业务代码和log4j的耦合太大。所以,如果我们能将日志输出定义为一个静态工具方法,改变这种情况。

定义一个静态工具类:

View Code
1 import org.apache.log4j.Logger ;
2 public class Logs{
3 private static Logger logger = Logger.getLogger(Logs.class);
4 //debug方法,其他error、warn类似定义
5 public static void debug(Object obj){
6 //如果更改日志输出工具,只需修改该行代码即可。如:可改为 System.out.println(obj);
7 logger.debug(obj);
8 }
9 }

这样我们就可以在其他类里用直接 Logs.debug("xxx"); 进行日志输出了。

而且,一旦我们改变了日志输出工具,只要修改 Logs类即可,其他业务类不必修改。

也许很多人也都能想到这么做,同时也有很多人会意识到这么做带来的另一个问题:源代码定位。

Log4j的日志信息,能够进行源代码定位——也就是说,能够在日志信息中输出,调用日志的类、方法及代码所在的行。

而当我们按上面方法改为静态输出后,就不能进行源代码定位了——虽然仍有源代码信息,但都是指向了Logs类。

这对程序员调试,以及跟踪、解决问题都不方便了。

不过既然Log4j能做到源代码定位,相信我们也会有办法做到,如果您有兴趣请继续看:

Log4j能准确捕获源代码的所在的类、方法、行。但java并没有提供响应的方法,这似乎很神奇。

上网搜一下 “Log4j 行号” 已经有高人指出:Log4j是通过java错误堆栈来实现的,也就是说通过new一个异常Throwable,然后再捕获,从而得到堆栈信息,在进行分析就可以得到行号等了。

所以,有人提出像log4j那样,抛出一个异常,然后捕获分析,从而在我们自己的静态日志工具里实现源代码定位,但是这样就多抛出一次异常,效率肯定低了。

而且抛出异常过多,引起额外事故也是个重大问题。

不管怎么说,这毕竟是一个思路,我尝试着找其他能得到堆栈信息的方法,最后在Thread类里找到了一个getStackTrace();

我没有深入研究,但感觉用这个方法得到堆栈应该比抛出异常好多了。

于是我对静态日志工具类,进行了一番改造,自我感觉还是不错的,下面贴出源码,欢迎讨论。

说明:该类有main方法以供测试。

View Code
  1 /**
2 * 日志输出代理类。
3 * <p>
4 * 主要完成业务代码和日志工具间的解耦,使切换日志工具更加方便。
5 * <p>
6 * 如:将Log4j改为commons-logging或自己的独立实现,则只需调整该类即可。
7 * <p>
8 * 简化了调用方式:如使用log4j,通常用法是先生成一个实例,再调用输出方法。
9 * 现在,只需直接使用 Logs.debug();等静态方法即可
10 *
11 * @author
12 * @version 1.0
13 * @since 1.5
14 */
15
16 public class Logs {
17 /**
18 * 是否打印日志,true表示打印日志,false表示不打印。
19 * <p>
20 * 开发阶段可以将其设为ture,运行阶段可以设为false
21 */
22 private static final boolean enabled = true;
23 /** 是否进行源代码定位,ture表示输出源代码所在类以及代码行 */
24 private static boolean showLocSrc = true;
25 /** 指定的日志级别 */
26 private static int level = 1;
27 /** 日志级别:普通 */
28 private static final int info = 1;
29 /** 日志级别:调试 */
30 private static final int debug = 2;
31 /** 日志级别:警告 */
32 private static final int warn = 3;
33 /** 日志级别:错误 */
34 private static final int error = 4;
35 /** 消息所属和消息内容的分隔符 */
36 private static final String msgSplit = ":";
37 /** 该类的名称,用于识别该类的堆栈 */
38 private static final String thisClassName = Logs.class.getName();
39
40 /** 默认输出日志的日志工具:log4j */
41 private static org.apache.log4j.Logger logger = null;
42 public Logs() {
43 }
44 static {
45 // 可以单独指定log4j的配置文件,也可以使用默认。
46 org.apache.log4j.PropertyConfigurator.configure("log4j.properties");
47 // 得到logger实例,作为输出工具。
48 // 此句作用是用一个输出实例,取代每个类里面的:Logger.getLogger(X.class)
49 logger = org.apache.log4j.Logger.getLogger("");
50 }
51 /**
52 * 测试
53 *
54 * @param args
55 */
56 public static void main(String args[]) {
57 Logs.debug("调试信息");
58 }
59 /**
60 * 根据日志级别,输出日志。
61 * <p>
62 * 如果要改变日志输出工具,
63 * <p>
64 * 如:由原来的log4j改为System.out.println()或logging,则只需改动此类即可。
65 *
66 * @param level
67 * 日志级别
68 * @param message
69 * 日志消息
70 * @param ste
71 * 堆栈信息。
72 * <p>
73 * 如果不需要输出源代码信息,则只需将静态成员属性 showLocSrc设为false即可。
74 */
75 private static void log(int level, Object message, StackTraceElement[] ste) {
76 if (ste != null) {
77 // 加入源代码定位
78 message = getStackMsg(ste) + msgSplit + message;
79 }
80 // 转入具体实现,此处为log4j,可以改为其他不同的日志实现。
81 switch (level) {
82 case info:
83 logger.info(message);
84 break;
85 case debug:
86 logger.debug(message);
87 break;
88 case warn:
89 logger.warn(message);
90 break;
91 case error:
92 logger.error(message);
93 break;
94 default:
95 logger.debug(message);
96 }
97 }
98 /**
99 * 根据堆栈信息得到源代码行信息
100 * <p>
101 * 原理:本工具类的堆栈下一行即为源代码的最原始堆栈。
102 * @param ste
103 * 堆栈信息
104 * @return 调用输出日志的代码所在的类.方法.代码行的相关信息
105 * <p>
106 * 如:com.MyClass 类里的 fun()方法调用了Logs.debug("xxx");
107 * <p>
108 * 则堆栈信息为: com.MyClass.fun(MyClass.java 代码行号)
109 */
110 private static String getStackMsg(StackTraceElement[] ste) {
111 if (ste == null)
112 return null;
113 boolean srcFlag = false;
114 for (int i = 0; i < ste.length; i++) {
115 StackTraceElement s = ste[i];
116 // 如果上一行堆栈代码是本类的堆栈,则该行代码则为源代码的最原始堆栈。
117 if (srcFlag) {
118 return s==null?"":s.toString();
119 }
120 // 定位本类的堆栈
121 if (thisClassName.equals(s.getClassName())) {
122 srcFlag = true;
123 }
124 }
125 return null;
126 }
127 /**
128 * 输出info信息
129 *
130 * @param message
131 */
132 public static void info(Object message) {
133 // 如果禁止日志或者输出级别不符,则返回。
134 if (!enabled || info < level)
135 return;
136 if (showLocSrc) {
137 log(info, message, Thread.currentThread().getStackTrace());
138 } else {
139 log(info, message, null);
140 }
141 }
142 /**
143 * 输出debug信息
144 *
145 * @param message
146 */
147 public static void debug(Object message) {
148 // 如果禁止日志或者输出级别不符,则返回。
149 if (!enabled || debug < level)
150 return;
151 if (showLocSrc) {
152 log(debug, message, Thread.currentThread().getStackTrace());
153 } else {
154 log(debug, message, null);
155 }
156 }
157 /**
158 * 输出warn信息
159 *
160 * @param message
161 */
162 public static void warn(Object message) {
163 // 如果禁止日志或者输出级别不符,则返回。
164 if (!enabled || warn < level)
165 return;
166 if (showLocSrc) {
167 log(warn, message, Thread.currentThread().getStackTrace());
168 } else {
169 log(warn, message, null);
170 }
171 }
172 /**
173 * 输出error信息
174 *
175 * @param message
176 */
177 public static void error(Object message) {
178 // 如果禁止日志或者输出级别不符,则返回。
179 if (!enabled || error < level)
180 return;
181 if (showLocSrc) {
182 log(error, message, Thread.currentThread().getStackTrace());
183 } else {
184 log(error, message, null);
185 }
186 }
187 }
posted @ 2011-09-28 15:23  暖日  阅读(811)  评论(1编辑  收藏  举报