文章目錄
- 埋點實現以及全流程日誌記錄(基於SSM的AOP)
- 1. 需求
- 2. 實現思路
- 3. 代碼實現
- 3.1 需求1
- 3.1.1 自定義註解 MyLog
- 3.1.2 切面類 AopLog
- 3.1.3 IAopLogService/AopLogServiceImpl
- 3.1.4 IAopLogDao
- 3.1.5 SysLogInfo
- 3.1.6 IAopLogDao.xml
- 3.2 需求2
- 3.2.1 自定義註解 MyTraceLog
- 3.2.2 切面類 TraceLog
- 3.2.3 攔截器 TraceInterceptor
- 3.2.4 生成 traceId TraceLogUtils
- 3.2.5 常量 Constants
- 3.2.6 請求上下文 RequestContext
- 3.3 Controller
- 4. 遇到的問題
歡迎訪問筆者個人技術博客:http://rukihuang.xyz/
埋點實現以及全流程日誌記錄(基於SSM的AOP)
1. 需求
- 由於項目需要,mentor給我佈置了一個埋點的開發任務,主要內容如下
- 需求1:記錄用戶的關鍵操作,並將用戶id,訪問時間,訪問接口,訪問的關鍵內容記錄下來,存到oracle數據庫中
- 需求2:記錄一次訪問的全流程,controller -> service -> dao,將該流程中執行的方法利用
logger
打印至控制檯,方便日後debug
2. 實現思路
- 實現思路基於小楊Vita的這一篇博客,原文鏈接:在Java項目中使用traceId跟蹤請求全流程日誌
- 需求1:基於AOP切面的思想,自定義一個註解
MyLog
,並將註解放置在Controller
接口方法。並將此接口作爲PointCut
,在前置通知中,記錄關鍵信息。 - 需求2:同樣是基於切面思想,自定義一個攔截器,在訪問前攔截每一個請求,給每個請求生成一個
traceId
,並在ThreadLocal
中放置一個traceId
副本;自定義一個註解TraceLog
,放置在controller
、service
、dao
的方法上,並將此接口作爲PointCut
,在環繞通知中,通過logger
在控制檯輸出信息。
3. 代碼實現
3.1 需求1
3.1.1 自定義註解 MyLog
import java.lang.annotation.*;
@Target({ElementType.PARAMETER, ElementType.METHOD}) //Annotation所修飾的對象範圍
@Retention(RetentionPolicy.RUNTIME) //生命週期
@Documented //產生doc文檔時會記錄
public @interface MyLog {
}
@Target
註解作用目標:@Target(ElementType.TYPE)
——接口、類、枚舉、註解@Target(ElementType.FIELD)
——字段、枚舉的常量@Target(ElementType.METHOD)
——方法@Target(ElementType.PARAMETER)
——方法參數@Target(ElementType.CONSTRUCTOR)
——構造函數@Target(ElementType.LOCAL_VARIABLE)
——局部變量@Target(ElementType.ANNOTATION_TYPE)
——註解@Target(ElementType.PACKAGE)
——包
@Retention
生命週期:RetentionPolicy.SOURCE
:註解只保留在源文件,當Java文件編譯成class文件的時候,註解被遺棄;RetentionPolicy.CLASS
:註解被保留到class文件,但jvm加載class文件時候被遺棄,這是默認的生命週期;RetentionPolicy.RUNTIME
:註解不僅被保存到class文件中,jvm加載class文件之後,仍然存在;
3.1.2 切面類 AopLog
@Component
@Aspect
public class AopLog {
private static Logger logger = Logger.getLogger(AopLog.class);
@Autowired
private IAopLogService aopLogService;
@Autowired
private HttpServletRequest request;
@Pointcut("@annotation(com.ruki.annotation.MyLog)")
public void aopLog(){}
@Before("aopLog()")
public void doBefore(JoinPoint jp) {
System.out.println("前置通知");
String params = request.getParameter("userId") == null ? "" : request.getParameter("userId");
String requestURI = request.getRequestURI();
String params = request.getParameter("data") == null ? "" : request.getParameter("data");
SysLogInfo info = new SysLogInfo();//參數實體類
info.setParam(params);
info.setRequest_method(requestURI);
try {
aopLogService.saveLog(info);
} catch (Exception e) {
logger.info(e.getMessage());
}
}
}
3.1.3 IAopLogService/AopLogServiceImpl
IAopLogService
@Service
public interface IAopLogService {
void saveLog(SysLogInfo sysLogInfo) throws Exception;
}
AopLogServiceImpl
@Service
public class AopLogServiceImpl implements IAopLogService {
@Autowired
private IAopLogDao aopLogDao;
@Override
public void saveLog(SysLogInfo sysLogInfo){
// System.out.println("進入dao了");
try {
aopLogDao.saveLog(sysLogInfo);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.1.4 IAopLogDao
public interface IAopLogDao {
void saveLog(SysLogInfo sysLogInfo) throws Exception;
}
3.1.5 SysLogInfo
public class SysLogInfo {
private String userId;
private String request_method;
private Integer data_locale;
private String data_time;
//getter setter
}
3.1.6 IAopLogDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruki.dao.IAopLogDao">
<insert id="saveLog" parameterType="com.ruki.model.SysLogInfo">
INSERT INTO
HH_SYSLOG(
USERID,
REQUEST_TIME,
REQUEST_METHOD
DARA
)
VALUES (
#{userId},
to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'),
#{request_method},
#{data}
)
</insert>
</mapper>
3.2 需求2
3.2.1 自定義註解 MyTraceLog
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyTraceLog {
}
3.2.2 切面類 TraceLog
@Component
@Aspect
public class TraceLog {
private static Logger traceLogger = Logger.getLogger(TraceLog.class);
@Pointcut("@annotation(com.ruki.annotation.MyTraceLog)")
public void traceLog(){}
@Around("traceLog()")
public Object doAround(ProceedingJoinPoint jp) {
String traceId = RequestContext.getTraceId();
String methodName = jp.getSignature().getName();
String className = jp.getTarget().getClass().getName();
Object object = null;
try {
traceLogger.info("traceId={"+traceId+"}, className={"+className+"},methodName={"+methodName+"},開始執行");
object = jp.proceed();
traceLogger.info("traceId={"+traceId+"}, className={"+className+"},methodName={"+methodName+"},執行結束");
} catch (Throwable throwable) {
traceLogger.error("traceId={"+traceId+"}, className={"+className+"},methodName={"+methodName+"},執行異常");
}
return object;
}
}
3.2.3 攔截器 TraceInterceptor
public class TraceInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = Logger.getLogger(TraceInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LOGGER.info("trace進入攔截器內");
String traceId = request.getHeader(Constants.LOG_TRACE_ID);//獲得traceId
if (traceId == null || traceId == "") {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("首次分配traceId");
}
traceId = TraceLogUtils.getTraceId();//生成traceId
}
RequestContext.addTraceId(traceId);//往ThreadLocal添加生成的traceId
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
RequestContext.removeTraceId();//訪問完成後清理ThreadLocal中的traceId
LOGGER.info("清理本次請求的trace信息完成");
super.afterCompletion(request, response, handler, ex);
}
}
3.2.4 生成 traceId TraceLogUtils
public class TraceLogUtils {
public static String getTraceId() {
return UUID.randomUUID().toString();
}
}
3.2.5 常量 Constants
public class Constants {
public static final String LOG_TRACE_ID = "trace_id";
}
3.2.6 請求上下文 RequestContext
- 將
traceId
在ThreadLocal
也放一份
public class RequestContext {
private final static ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();
public static void addTraceId(String traceId) {
traceIdThreadLocal.set(traceId);
}
public static String getTraceId() {
return traceIdThreadLocal.get();
}
public static void removeTraceId() {
traceIdThreadLocal.remove();
}
}
3.3 Controller
@RestController
@RequestMapping("/hello")
public class HelloWorld {
@Autowired
private IHelloService helloService;
@MyLog
@MyTraceLog
@RequestMapping(value = { "/say" }, method = { RequestMethod.GET })
public String sayHello(){
System.out.println("hello world");
helloService.sayHello();
return "hello world!";
}
}
4. 遇到的問題
- 在實現需求2的時編寫了一個攔截器
TraceInterceptor
,使用註解方式註冊攔截器未生效,在xml文件中配置可以生效,暫時還不明白時什麼原因。
不生效
@Configuration
public class InceptorRegisConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
生效
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.ruki.interceptor.TraceInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>