本文是转载网上的一篇文章
对于开发和调试以及上线运营,日志必不可少,常用日志工具有:Log4j、common-logging、甚至System.out。
这里用Log4j为例,说说如何将日志输出和业务代码做到最大分离。(篇幅不长,最后是源代码)
Logger4j 一般使用方法:
1.定义配置文件:log4j.properties
2.在需要输出日志的类里获取Logger实例:Logger loger = Logger.getLogger(AAA.class);
3.输出日志:loger.debug("test");
大家这么做都已经轻车熟路了,但是这样做有两个问题:
(1)每个需要输出日志信息的类里,都要引入log4j的包,都要定义一个Logger实例。
(2)如果一旦系统由log4j改为别的日志输出方式,如System.out 或common-logging,则要修改N多个类的代码。
以上两点,使得业务代码和log4j的耦合太大。所以,如果我们能将日志输出定义为一个静态工具方法,改变这种情况。
定义一个静态工具类:
import org.apache.log4j.Logger ; public class Logs{ private static Logger logger = Logger.getLogger(Logs.class); //debug方法,其他error、warn类似定义 public static void debug(Object obj){ //如果更改日志出工工具,只需修改该行代码即可。如:可改为 System.out.println(obj); logger.debug(obj); } }
这样我们就可以在其他类里用直接 Logs.debug("test"); 进行日志输出了。
而且,一旦我们改变了日志输出工具,只要修改 Logs类即可,其他业务类不必修改。
也许很多人也都能想到这么做,同时也有很多人会意识到这么做带来的另一个问题:源代码定位。
Log4j的日志信息,能够进行源代码定位——也就是说,能够在日志信息中输出,调用日志的类、方法及代码所在的行。
而当我们按上面方法改为静态输出后,就不能进行源代码定位了——虽然仍有源代码信息,但都是指向了Logs类。
这对程序员调试,以及跟踪、解决问题都不方便了。
不过既然Log4j能做到源代码定位,相信我们也会有办法做到,如果您有兴趣请继续看:
-------------------------------------------------------------------
Log4j能准确捕获源代码的所在的类、方法、行。但java并没有提供响应的方法,这似乎很神奇。
上网搜一下 “Log4j 行号” 已经有高人指出:Log4j是通过java错误堆栈来实现的,也就是说通过new一个异常Throwable,然后在捕获,从而得到堆栈信息,在进行分析就可以得到行号等了。
所以,有人提出象log4j那样,抛出一个异常,然后捕获分析,从而在我们自己的静态日志工具里实现源代码定位,但是这样就多抛出一次异常,效率肯定底了。
而且抛出异常过多,引起额外事故也是个重大问题。
不管怎么说,这毕竟是一个思路,我尝试着找其他能得到堆栈信息的方法,最后在Thread类里找到了一个getStackTrace();
这个方法是jdk1.5版本以后才有的,幸亏我用的是1.5。
我没有深入研究,但感觉用这个方法得到堆栈应该比抛出异常好多了。
于是我对静态日志工具类,进行了一番改造,自我感觉还是不错的,下面贴出源码,欢迎讨论。
说明:该类有main方法以供测试。
-----------------------------------------
/** * 日志输出代理类。 * <p> * 主要完成业务代码和日志工具间的解耦,使切换日志工具更加方便。 * <p> * 如:将Log4j改为common-logging或自己的独立实现,则只需调整该类即可。 * <p> * 简化了调用方式:如使用log4j,通常用法是先生成一个实例,再调用输出方法。 * 现在,只需直接使用 Logs.debug();等静态方法即可 * * @author yangzi<p> * 2008-09-08 * @version 1.0 * @since 1.5 */ public class Logs { /** * 是否打印日志,true表示打印日志,false表示不打印。 * <p> * 开发阶段可以将其设为ture,运行阶段可以设为false */ private static final boolean enabled = true; /** 是否进行源代码定位,ture表示输出源代码所在类以及代码行 */ private static boolean showLocSrc = true; /** 指定的日志级别 */ private static int level = 1; /** 日志级别:普通 */ private static final int info = 1; /** 日志级别:调试 */ private static final int debug = 2; /** 日志级别:警告 */ private static final int warn = 3; /** 日志级别:错误 */ private static final int error = 4; /** 消息所属和消息内容的分隔符 */ private static final String msgSplit = ":"; /** 该类的名称,用于识别该类的堆栈 */ private static final String thisClassName = Logs.class.getName(); /** 默认输出日志的日志工具:log4j */ private static org.apache.log4j.Logger logger = null; public Logs() { } static { // 可以单独指定log4j的配置文件,也可以使用默认。 // org.apache.log4j.PropertyConfigurator.configure("log4j.properties"); // 得到logger实例,作为输出工具。 // 此句作用是用一个输出实例,取代每个类里面的: Logger.getLogger(X.class) logger = org.apache.log4j.Logger.getLogger(""); } /** * 测试 * * @param args */ public static void main(String args[]) { Logs.debug("调试信息"); } /** * 根据日志级别,输出日志。 * <p> * 如果要改变日志输出工具, * <p> * 如:由原来的log4j改为System.out.println()或logging,则只需改动此类即可。 * * @param level * 日志级别 * @param message * 日志消息 * @param ste * 堆栈信息。 * <p> * 如果不需要输出源代码信息,则只需将静态成员属性 showLocSrc设为false即可。 */ private static void log(int level, Object message, StackTraceElement[] ste) { if (ste != null) { // 加入源代码定位 message = getStackMsg(ste) + msgSplit + message; } // 转入具体实现,此处为log4j,可以改为其他不同的日志实现。 switch (level) { case info: logger.info(message); break; case debug: logger.debug(message); break; case warn: logger.warn(message); break; case error: logger.error(message); break; default: logger.debug(message); } } /** * 根据堆栈信息得到源代码行信息 * <p> * 原理:本工具类的堆栈下一行即为源代码的最原始堆栈。 * * @param ste * 堆栈信息 * @return 调用输出日志的代码所在的类.方法.代码行的相关信息 * <p> * 如:com.MyClass 类里的 fun()方法调用了Logs.debug("test"); * <p> * 则堆栈信息为: com.MyClass.fun(MyClass.java 代码行号) */ private static String getStackMsg(StackTraceElement[] ste) { if (ste == null) return null; boolean srcFlag = false; for (int i = 0; i < ste.length; i++) { StackTraceElement s = ste[i]; // 如果上一行堆栈代码是本类的堆栈,则该行代码则为源代码的最原始堆栈。 if (srcFlag) { return s==null?"":s.toString(); } // 定位本类的堆栈 if (thisClassName.equals(s.getClassName())) { srcFlag = true; } } return null; } /** * 输出info信息 * * @param message */ public static void info(Object message) { // 如果禁止日志或者输出级别不符,则返回。 if (!enabled || info < level) return; if (showLocSrc) { log(info, message, Thread.currentThread().getStackTrace()); } else { log(info, message, null); } } /** * 输出debug信息 * * @param message */ public static void debug(Object message) { // 如果禁止日志或者输出级别不符,则返回。 if (!enabled || debug < level) return; if (showLocSrc) { log(debug, message, Thread.currentThread().getStackTrace()); } else { log(debug, message, null); } } /** * 输出warn信息 * * @param message */ public static void warn(Object message) { // 如果禁止日志或者输出级别不符,则返回。 if (!enabled || warn < level) return; if (showLocSrc) { log(warn, message, Thread.currentThread().getStackTrace()); } else { log(warn, message, null); } } /** * 输出error信息 * * @param message */ public static void error(Object message) { // 如果禁止日志或者输出级别不符,则返回。 if (!enabled || error < level) return; if (showLocSrc) { log(error, message, Thread.currentThread().getStackTrace()); } else { log(error, message, null); } } }