log4j2之简化封装,告别静态成员变量
2016-10-15 21:43
295 查看
注
本文是使用 slf4j + log4j2 示例,由于 slf4j 只是一个统一接口包,log4j / log4j2 / logback 等都是有其实现类,所以本文中是以 slf4j 为例。若有朋友坚持不使用 slf4j ,则将代码中 slf4j 相关的都做对应更改即可,并不麻烦。序
一般情况下,每当我们使用 slf4j 等log组件时,都是在需要记载日志的类中,创建一个静态的 Logger 成员变量,然后调用 debug,info,error 等方法。这就意味着我们每一个要记载日志的类,都需要先定义一个静态的成员变量,这样才打印出正确的前缀(带有类名称)一、现状
我们以码云中几个热门的java项目为例(下面的代码片段都是 star 超过 500+ 的项目)public class DepartController { private static final Logger logger = Logger.getLogger(DepartController.class); ... method() { logger.info("some things!"); } }
@Controller public class LoginController extends BaseController { private static final Logger LOGGER = LogManager.getLogger(LoginController.class); ... method() { LOGGER.info("some things!"); } }
public class AccountAction extends BaseAction<Account> { private static final Logger logger = LoggerFactory.getLogger(AccountAction.class); ... method() { logger.info("some things!"); } }
二、假设一个更优的 Logger
不知道大家是否有想过,要是有一个 Logger 直接提供静态的 debug/info/error 方法,就不用每个类都要去定义静态的成员变量了,比方说下面的代码(为了不被误解,我们将封装过的 Logger 命名为 GjpLogger ):public class LoginController extends BaseController { ... method() { GjpLogger.info("some things!"); } }
注意,我们没有定义静态的 Logger 成员变量!
三、如何封装实现假设的 Logger
1、封装前科普:java.lang.StackTraceElement
该类元素代表一个堆栈帧。除了一个在堆栈的顶部所有的栈帧代表一个方法调用。在堆栈顶部的帧表示在将其生成的堆栈跟踪的执行点。该类包含4个可用的get方法:getClassName()、getMethodName()、getLineNumber()、getFileName()
(细心的同学估计已经猜到了,没错!我们就从它下手,获取出被调用的那个方法所属的 类名、方法名以及当前行号)
2、封装前科普:Thread.currentThread().getStackTrace()
该方法返回一个代表该线程的堆栈转储堆栈跟踪元素的数组。这将返回一个零长度数组,如果该线程尚未启动或已经终止。举个例子,我们调用一下项目中的 MainController.login() 方法,看可以打印出什么
StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); for (StackTraceElement s : callStack) { System.out.println("s.getClassName() -> " + s.getClassName()); System.out.println("s.getMethodName() -> " + s.getMethodName()); System.out.println("s.getLineNumber() -> " + s.getLineNumber()); }
s.getClassName() -> java.lang.Thread s.getMethodName() -> getStackTrace s.getLineNumber() -> 1568 s.getLineNumber() -> 69 s.getClassName() -> com.guijianpan.framework.log.GjpLogger s.getMethodName() -> info s.getLineNumber() -> 94 s.getClassName() -> com.guijianpan.system.controller.MainController s.getMethodName() -> login s.getLineNumber() -> 34 s.getClassName() -> com.guijianpan.system.controller.MainController$$FastClassBySpringCGLIB$$d0dda813 s.getMethodName() -> invoke s.getLineNumber() -> -1 s.getClassName() -> org.springframework.cglib.proxy.MethodProxy s.getMethodName() -> invoke s.getLineNumber() -> 204 s.getClassName() -> org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation s.getMethodName() -> invokeJoinpoint s.getLineNumber() -> 717 s.getClassName() -> org.springframework.aop.framework.ReflectiveMethodInvocation s.getMethodName() -> proceed s.getLineNumber() -> 157
我们可以看到,打印出了很多信息,实际上,我们要的只是 上面日志的 第8-10行内容
3、获取想要的 StackTraceElement
public static StackTraceElement findCaller() { // 获取堆栈信息 StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); if(null == callStack) return null; // 最原始被调用的堆栈信息 StackTraceElement caller = null; // 日志类名称 String logClassName = GjpLogger.class.getName(); // 循环遍历到日志类标识 boolean isEachLogClass = false; // 遍历堆栈信息,获取出最原始被调用的方法信息 for (StackTraceElement s : callStack) { // 遍历到日志类 if(logClassName.equals(s.getClassName())) { isEachLogClass = true; } // 下一个非日志类的堆栈,就是最原始被调用的方法 if(isEachLogClass) { if(!logClassName.equals(s.getClassName())) { isEachLogClass = false; caller = s; break; } } } return caller; }
到此,我们就取到了 MainController.login() 的 StackTraceElement 对象
4、封装 logger -> debug / info / error
private static Logger logger() { StackTraceElement caller = findCaller(); if(null == caller) return LoggerFactory.getLogger(GjpLogger.class); // 实例化一个原始被调用的类 Logger 对象,并且带上 方法名称、行号,更方便的通过日志定位代码 Logger log = LoggerFactory.getLogger(caller.getClassName() + "." + caller.getMethodName() + "() Line: " + caller.getLineNumber()); return log; } /** * 静态的 debug 方法 */ public static void debug(String msg) { debug(msg, null); }
5、调用 logger
public class MainController { public ModelAndView login() { GjpLogger.info("test log info!"); return render("login/login"); } }
6、logger 结果
2016-10-14 22:56:12 INFO com.guijianpan.system.controller.MainController.login() Line: 34 - test log info!
附:GjpLogger.java
package com.guijianpan.framework.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Gjp日志工具类 {logger name 值为 [className].[methodName]() Line: [fileLine]} <br/>
* 若要自定义可配置打印出执行的方法名和执行行号位置等信息,请参考RequestLoggerLogger.java<br/>
* @see com.guijianpan.framework.log.log4j.RequestLogger
* @author yzChen
* @date 2016年10月13日 下午11:50:59
*/
public class GjpLogger {
/**
* 获取最原始被调用的堆栈信息
* @return
* @author yzChen
* @date 2016年10月13日 下午11:50:59
*/
public static StackTraceElement findCaller() { // 获取堆栈信息 StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); if(null == callStack) return null; // 最原始被调用的堆栈信息 StackTraceElement caller = null; // 日志类名称 String logClassName = GjpLogger.class.getName(); // 循环遍历到日志类标识 boolean isEachLogClass = false; // 遍历堆栈信息,获取出最原始被调用的方法信息 for (StackTraceElement s : callStack) { // 遍历到日志类 if(logClassName.equals(s.getClassName())) { isEachLogClass = true; } // 下一个非日志类的堆栈,就是最原始被调用的方法 if(isEachLogClass) { if(!logClassName.equals(s.getClassName())) { isEachLogClass = false; caller = s; break; } } } return caller; }
/**
* 自动匹配请求类名,生成logger对象,此处 logger name 值为 [className].[methodName]() Line: [fileLine]
* @return
* @author yzChen
* @date 2016年10月13日 下午11:50:59
*/
private static Logger logger() {
// 最原始被调用的堆栈对象
StackTraceElement caller = findCaller();
if(null == caller) return LoggerFactory.getLogger(GjpLogger.class);
Logger log = LoggerFactory.getLogger(caller.getClassName() + "." + caller.getMethodName() + "() Line: " + caller.getLineNumber());
return log;
}
public static void trace(String msg) {
trace(msg, null);
}
public static void trace(String msg, Throwable e) {
logger().trace(msg, e);
}
public static void debug(String msg) {
debug(msg, null);
}
public static void debug(String msg, Throwable e) {
logger().debug(msg, e);
}
public static void info(String msg) {
info(msg, null);
}
public static void info(String msg, Throwable e) {
logger().info(msg, e);
}
public static void warn(String msg) {
warn(msg, null);
}
public static void warn(String msg, Throwable e) {
logger().warn(msg, e);
}
public static void error(String msg) {
error(msg, null);
}
public static void error(String msg, Throwable e) {
logger().error(msg, e);
}
}
四、附录
1、关于获取堆栈代码的质疑
由于评论中很多童鞋都在问关于直接读取堆栈信息的问题,比如说性能方面。性能的话,本人一般都不会深究,具体原因就不说了,当然,最大的原因是有点懒。这里,本人另外提一下关于 Log4j2 最终打印控制台时的一部分源码,它也是读取了堆栈信息,循环遍历。以下是源代码(log4j-api-2.7.jar -> org.apache.logging.log4j.status.StatusLogger.java -> Line 229 再跳转到 Line 249)
private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) { if (fqcn == null) { return null; } boolean next = false; for (final StackTraceElement element : stackTrace) { final String className = element.getClassName(); if (next && !fqcn.equals(className)) { return element; } if (fqcn.equals(className)) { next = true; } else if (NOT_AVAIL.equals(className)) { break; } } return null; }
2、Log4j2.xml 环境搭建及基本配置
Log4j2.xml 环境搭建及基本配置My Blog
blog.guijianpan.com相关文章推荐
- ASP.NET开发,简化与封装
- Servlet-简化Servlet中获取表单信息及封装信息的过程的RequestToBeanUtil类
- log4j中isDebugEnabled(), log.isInfoEnabled()和log.isTraceEnabled的封装
- jQuery EasyUI封装简化操作
- Activity,Fragment的基类封装,简化findViewById,Fragment懒加载和不重复加载等
- ASP.NET开发,简化与封装
- 最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)
- Commons: 封装Apache Commons Email简化邮件发送
- 基于OkHttpUtils再次封装代码最简化
- 对Jquery中的ajax再封装,简化操作示例
- JedisUtil再封装(基于连接池,简化代码)
- 在次封装easyui 的window插件,便于简化操作
- Linux下Socket的简单使用及最简化封装
- 从头开始学 RecyclerView(三) 封装简化
- RecyclerView_QuickAdapter及相关封装简化
- 从零开始搭建一个完善的MVP开发框架(四) —对View(Activity,Fragment等)层组件进行封装简化View层的开发
- 使用AOP封装的权限动态申请库PermissionLibrary,简化针对Android6.0之后实现权限动态申请的复杂性
- 对AsyncTask进行封装,简化繁琐的异步操作
- 轻松把玩HttpClient之封装HttpClient工具类(六),封装输入参数,简化工具类
- 大话分页(补充)——Threadlocal封装offSet和pageSize简化分页工具类