改善Log4j日志输出――将日志输出改为静态方法

[转载  http://hi.baidu.com/piaokes/blog/item/ec21903595ab208da61e1213.html]
对于开发和调试以及上线运营,日志必不可少,常用日志工具有: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);
   }
}
}

你可能感兴趣的:(log4j,职场,休闲)