当我们在多线程的环境下需要跟踪某一线程的产生的日志,如果没有一个用于标注日志所对应的线程的标识,那么查日志会是一个很痛苦的过程。
在日志中为不同线程加上唯一的标识是一个不错的解决办法,Log4j已经其实已经提供了多种实现方式,这里要说的是通过DNC的方式为日志加上线程标识,其他的方法请参考这里:https://blog.csdn.net/liulin_good/article/details/5995884
假设:当我们收到一个请求之后,业务层或持久层报了错,我们如何跟踪判断这个请求从前到后的所有日志?
简单的说,spring容器会为每一个请求分配一个线程,既然是要从收到请求开始跟踪,那么我们就在controller层做切面,收到请求后创建id并push到DNC中,并在请求处理结束时后,清除掉我们push到DNC中的id,下面上代码:
controller:
@Controller
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/getUsername")
@ResponseBody
public User getUserById(Integer id) throws Exception {
User user = userService.getUserById(id);
return user;
}
}
service:(故意写一个会报错的代码,用于一会测试)
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUserById(Integer id) throws Exception {
try {
int i = 1 / 0;
System.out.println(i);
} catch (Exception e) {
LogUtils.info("UserServiceImpl执行出错,入参为:id=" + id);
throw new Exception("出错啦!!!", e);
}
User user = userMapper.getUserById(id);
return user;
}
}
日志实现类:
package com.test.log;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class LogAspect {
Logger logger = Logger.getLogger(LogAspect.class);
String logStr = null;
/**
* 前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点前的执行
*
* @param jp
* 连接点:程序执行过程中的某一行为
*/
public void doBefore(JoinPoint jp) {
logStr = jp.getTarget().getClass().getName() + "类的" + jp.getSignature().getName() + "方法开始执行******Start******\n";
logger.info(logStr);
}
/**
* 环绕通知:包围一个连接点的通知,可以在方法的调用前后完成自定义的行为,也可以选择不执行。
* 类似web中Servlet规范中Filter的doFilter方法。
*
* @param pjp
* 当前进程中的连接点
* @return
*/
public Object doAround(ProceedingJoinPoint pjp) {
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
StringBuilder logInfo = new StringBuilder(
"方法:" + pjp.getTarget().getClass() + "." + pjp.getSignature().getName() + "()\n");
logInfo.append("提示信息为:" + e.getMessage() + "\n");
logInfo.append("具体错误信息如下:\n");
StackTraceElement[] stackTrace = e.getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
logInfo.append(stackTraceElement + "\n");
}
logger.info(logInfo);
}
return result;
}
/**
* 后置通知
*
* @param jp
*/
public void doAfter(JoinPoint jp) {
logStr = jp.getTarget().getClass().getName() + "类的" + jp.getSignature().getName() + "方法执行结束******End******\n";
logger.info(logStr);
}
}
生成日志id实现类:
package com.test.log;
import java.util.UUID;
import org.apache.log4j.NDC;
public class LogId {
/**
* 通过uuid为线程标注唯一标识
*/
public void pushLogId() {
NDC.push(UUID.randomUUID().toString());
}
/**
* 记得清除一下
*/
public void removeLogId() {
NDC.remove();
}
}
在spring的配置文件applicationContext.xml中配置业务层和持久层的日志切面:
<!-- 日志 -->
<bean id="logAspect" class="com.test.log.LogAspect"></bean>
<aop:config>
<!-- controller日志 -->
<aop:pointcut id="serviceLogPointcut"
expression="execution(* com.test.*.*.*(..))" />
<aop:aspect id="aspect" ref="logAspect">
<aop:before pointcut-ref="serviceLogPointcut"
method="doBefore" />
<aop:after pointcut-ref="serviceLogPointcut"
method="doAfter" />
<aop:around pointcut-ref="serviceLogPointcut"
method="doAround" />
</aop:aspect>
</aop:config>
在springMVC的配置文件springmvc.xml中配置controller的日志切面,并生成线程标识id:
<bean id="logId" class="com.test.log.LogId"></bean>
<aop:config>
<aop:pointcut id="controllerLogPointcut"
expression="execution(* com.test.controller.*.*(..))" />
<!-- 生成日志id -->
<aop:aspect id="logIdAspect" ref="logId" order="1">
<aop:before pointcut-ref="controllerLogPointcut"
method="pushLogId" />
<aop:after pointcut-ref="controllerLogPointcut"
method="removeLogId" />
</aop:aspect>
<!-- controller日志 -->
<aop:aspect id="aspect" ref="logAspect" order="2">
<aop:before pointcut-ref="contr