每日一博|log4j2 之简化封装,告别静态成员变量

注 本文是使用 slf4j + log4j2 示例,由于 slf4j 只是一个统一接口包,log4j log4j2 logback 等都是有其实现类,

每日一博|log4j2 之简化封装,告别静态成员变量

本文是使用 slf4j + log4j2 示例,由于 slf4j 只是一个统一接口包,log4j / log4j2 / logback 等都是有其实现类,所以本文中是以 slf4j 为例。若有朋友坚持不使用 slf4j ,则将代码中 slf4j 相关的都做对应更改即可,并不麻烦。

一般情况下,每当我们使用 slf4j 等log组件时,都是在需要记载日志的类中,创建一个静态的 Logger 成员变量,然后调用 debug,info,error 等方法。这就意味着我们每一个要记载日志的类,都需要先定义一个静态的成员变量,这样才打印出正确的前缀(带有类名称)

一、现状

我们以码云中几个热门的java项目为例(下面的代码片段都是 star 超过 500+ 的项目)

` java

public class DepartController {

private static final Logger logger = Logger.getLogger(DepartController.class);

... method() {

logger.info("some things!");

}

}

`

java @Controller public class LoginController extends BaseController { private static final Logger LOGGER = LogManager.getLogger(LoginController.class); ... method() { LOGGER.info("some things!"); } }

`

java

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 ):

` java

public class LoginController extends BaseController {

... method() {

GjpLogger.info("some things!");

}

}

三、如何封装实现假设的 Logger

1、封装前科普:java.lang.StackTraceElement

该类元素代表一个堆栈帧。除了一个在堆栈的顶部所有的栈帧代表一个方法调用。在堆栈顶部的帧表示在将其生成的堆栈跟踪的执行点。

该类包含4个可用的get方法:getClassName()、getMethodName()、getLineNumber()、getFileName()

(细心的同学估计已经猜到了,没错!我们就从它下手,获取出被调用的那个方法所属的 类名、方法名以及当前行号)

2、封装前科普:Thread.currentThread().getStackTrace()

该方法返回一个代表该线程的堆栈转储堆栈跟踪元素的数组。这将返回一个零长度数组,如果该线程尚未启动或已经终止。

举个例子,我们调用一下项目中的 MainController.login() 方法,看可以打印出什么 ``` java 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()); }

`

console

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

``` java 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

``` java 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

` java

public class MainController {

public ModelAndView login() {

GjpLogger.info("test log info!");

return render("login/login");

}

}

6、logger 结果

` console

2016-10-14 22:56:12 INFO com.guijianpan.system.controller.MainController.login() Line: 34 - test log info!

附:GjpLogger.java

``` java package com.guijianpan.framework.log;

import org.slf4j.Logger; import org.slf4j.LoggerFactory;

/*

* Gjp日志工具类 {logger name 值为 [className]. methodName Line: [fileLine]}

* 若要自定义可配置打印出执行的方法名和执行行号位置等信息,请参考RequestLoggerLogger.java

* @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);

}

}

```

My Blog

blog.chenyuanzhen.com

未登录用户
全部评论0
到底啦