最近开发时突然感觉每次需要打印日志信息时都要在当前类中定义一个Logger对象特别的麻烦,所以就在想能不能使用一个日志工具类里面定义一些打印日志的静态方法,这样在需要打印日志时直接使用工具类的静态方法。
有了这个想法后就开始尝试,其实这个需求很简单,看下面例子:
public class LoggerUtil { private final static Logger LOGGER = LoggerFactory.getLogger(LoggerUtil.class); public static void info(String msg) { LOGGER.info(msg); } public static void main(String[] args) { LoggerUtil.info("logger info"); } }
这样就可以实现使用工具类的静态方法打印日志了,但存在一个问题,无论在哪个类中使用LoggerUtil.info()打印出来的日志信息显示的类命名都为
2017-12-12 11:12:55.381 [main] INFO com.dzcx.report.common.utils.LoggerUtil - logger info
这样无法从日志信息中很快的定位到是哪个类打印的日志,不利于查错,所以这种方案不适用,需优化。
有一点可以确认,就是日志信息显示的类全名是由LoggerFactory.getLogger()方法时传入的对象决定的。
LoggerFactory是sl4j的实现类,它的getLogger方法有两个重载的实现
public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } /** * Return a logger named corresponding to the class passed as parameter, using * the statically bound {@link ILoggerFactory} instance. * * @param clazz the returned logger will be named after clazz * @return logger */ public static Logger getLogger(Class clazz) { return getLogger(clazz.getName()); }
看源码可知,两个重载的方法参数不一样,具体实现其实是一样的。sl4j是日志的通用接口,logback是其中的一种实现,logback的具体实现在LoggerContext中,看下面代码
public final Logger getLogger(final Class clazz) { return getLogger(clazz.getName()); } public final Logger getLogger(final String name) { if (name == null) { throw new IllegalArgumentException("name argument cannot be null"); } // if we are asking for the root logger, then let us return it without // wasting time if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) { return root; } int i = 0; Logger logger = root; // check if the desired logger exists, if it does, return it // without further ado. Logger childLogger = (Logger) loggerCache.get(name); // if we have the child, then let us return it without wasting time if (childLogger != null) { return childLogger; } // if the desired logger does not exist, them create all the loggers // in between as well (if they don't already exist) String childName; while (true) { int h = LoggerNameUtil.getSeparatorIndexOf(name, i); if (h == -1) { childName = name; } else { childName = name.substring(0, h); } // move i left of the last point i = h + 1; synchronized (logger) { childLogger = logger.getChildByName(childName); if (childLogger == null) { childLogger = logger.createChildByName(childName); loggerCache.put(childName, childLogger); incSize(); } } logger = childLogger; if (h == -1) { return childLogger; } } }
LoggerContext中使用了Map<String, Logger> loggerCache缓存了所有日志对象,且它的key就是日志对象当前的类全名
回到上面的问题,既然Logback使用类命名缓存了所有的日志对象,那我们只要知道当前是在哪个类中调用的LoggerUtil.info方法,我们就可以从logback的缓存日志对象中获取到对应的日志对象,使用该日志对象打印日志就能解决上面的问题,那接下来的问题就是如何在LoggerUtil的静态方法中获取当前调用类的全名。通过网上搜索找到解决办法,最终实现一个还算完美的日志静态工具类,代码如下:
/** * 日志工具类,使用静态方法打印日志 无需每个类中定义日志对象 * Logback对每个Logger对象做了缓存,每次调用LoggerFactory.getLogger(String name)时如果已存在则从缓存中获取不会生成新的对象; * 同时也不会有对象的创建与销毁造成的性能损失 * @author zsc * @datetime 2017年12月12日 上午11:43:51 */ public class LoggerUtil { public static void error(String msg) { LoggerFactory.getLogger(getClassName()).error(msg); } public static void error(String msg, Object... obj) { LoggerFactory.getLogger(getClassName()).error(msg, obj); } public static void warn(String msg) { LoggerFactory.getLogger(getClassName()).error(msg); } public static void warn(String msg, Object... obj) { LoggerFactory.getLogger(getClassName()).error(msg, obj); } public static void info(String msg) { LoggerFactory.getLogger(getClassName()).info(msg); } public static void info(String msg, Object... obj) { LoggerFactory.getLogger(getClassName()).info(msg, obj); } public static void debug(String msg) { LoggerFactory.getLogger(getClassName()).debug(msg); } public static void debug(String msg, Object... obj) { LoggerFactory.getLogger(getClassName()).debug(msg, obj); } // 获取调用 error,info,debug静态类的类名 private static String getClassName() { return new SecurityManager() { public String getClassName() { return getClassContext()[3].getName(); } }.getClassName(); } }