1.關(guān)于系統(tǒng)日志表結(jié)構(gòu)的設(shè)計:
CREATE TABLE `sys_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用戶id',
`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用戶名',
`operation` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用戶操作',
`time` int(11) NULL DEFAULT NULL COMMENT '響應時間',
`method` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '請求方法',
`params` varchar(5000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '請求參數(shù)',
`ip` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'IP地址',
`gmt_create` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時間',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 798 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系統(tǒng)日志' ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
2.POM的引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
3.基于注解化的AOP自定義Web請求日志處理:
@interface 注解詳解
Spring Aop的作用
1.@Log()注解聲明Class:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*Target注解決定Log注解可以加在哪些成分上品抽,
如加在類身上雷袋,或者屬性身上叉瘩,或者方法身上等成分*/
@Target(ElementType.METHOD)
/*Retention注解決定MyAnnotation注解的生命周期*/
@Retention(RetentionPolicy.RUNTIME)
/*
這里是在注解類MyAnnotation上使用另一個注解類,這里的Retention稱為元注解戒傻。
Retention注解括號中的"RetentionPolicy.RUNTIME"
意思是讓MyAnnotation這個注解的生命周期一直程序運行時都存在
*/
public @interface Log {
String value() default "";
}
2.使用Log注解的請求日志處理Class(將攔截到的數(shù)據(jù)存入系統(tǒng)日志表中)
import com.alibaba.fastjson.JSONObject;
import com.itcast.commom.annotation.Log;
import com.itcast.commom.utils.HttpContextUtils;
import com.itcast.commom.utils.IPUtils;
import com.itcast.demo.entity.SysLog;
import com.itcast.demo.service.SysLogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
//使用aop來完成基于自定義注解的請求日志處理將其存入數(shù)據(jù)庫
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Autowired
SysLogService logService;
/**
* 功能描述:定義一個切入點
*
* @date: 2019/7/2 9:42
*/
@Pointcut("@annotation(com.itcast.commom.annotation.Log)")
public void logPointCut() {
}
//環(huán)繞通知
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 執(zhí)行方法
Object result = point.proceed();
// 執(zhí)行時長(毫秒)
long time = System.currentTimeMillis() - beginTime;
//TODO 異步保存日志
saveLog(point, time);
return result;
}
/*
*保存到數(shù)據(jù)庫
*/
void saveLog(ProceedingJoinPoint joinPoint, long time) throws InterruptedException {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
Log syslog = method.getAnnotation(Log.class);
if (syslog != null) {
// 注解上的描述
sysLog.setOperation(syslog.value());
}
// 請求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
// 請求的參數(shù)
Object[] args = joinPoint.getArgs();
try {
//轉(zhuǎn)為JOSN串
String params = JSONObject.toJSONString(args);
//Arrays.toString(args)
sysLog.setParams(params);
} catch (Exception e) {
}
// 獲取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// 設(shè)置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
//用戶id
sysLog.setUserId(new Long(123));
//用戶名
sysLog.setUsername("用戶名");
sysLog.setTime((int) time);
// 系統(tǒng)當前時間
Date date = new Date();
sysLog.setGmtCreate(date);
logService.save(sysLog);
}
}
3.HttpContextUtils工具類:
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class HttpContextUtils {
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
}
4.基于AOP的全局請求日志處理
參考文章
1.AOP請求處理Class:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
//使用aop來完成全局請求日志處理
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
//切點
//兩個..代表所有子目錄,最后括號里的兩個..代表所有參數(shù)
@Pointcut("execution( * com.itcast..controller.*.*(..))")
public void logPointCut() {
}
//前置通知
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到請求秧秉,記錄請求內(nèi)容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄下請求內(nèi)容
logger.info("請求地址 : " + request.getRequestURL().toString());
logger.info("HTTP METHOD : " + request.getMethod());
// 獲取真實的ip地址
//logger.info("IP : " + IPAddressUtil.getClientIpAddress(request));
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName());
logger.info("參數(shù) : " + Arrays.toString(joinPoint.getArgs()));
}
//后置運行通知
@AfterReturning(returning = "ret", pointcut = "logPointCut()")// returning的值和doAfterReturning的參數(shù)名一致
public void doAfterReturning(Object ret) throws Throwable {
// 處理完請求空盼,返回內(nèi)容(返回值太復雜時,打印的是物理存儲空間的地址)
logger.debug("返回值 : " + ret);
}
//環(huán)繞最終通知,final增強匙监,不管是拋出異撤渤鳎或者正常退出都會執(zhí)行
@Around("logPointCut()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
Object ob = pjp.proceed();// ob 為方法的返回值
logger.info("耗時 : " + (System.currentTimeMillis() - startTime));
return ob;
}
}
5.logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>logback</contextName>
<!--輸出到控制臺-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--按天生成日志-->
<appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Prudent>true</Prudent>
<!--所存放的目錄-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>
applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log
</FileNamePattern>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss} -%msg%n
</Pattern>
</layout>
</appender>
<logger name="com.bootdo" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="logFile" />
</logger>
<root level="error">
<appender-ref ref="console"/>
<appender-ref ref="logFile" />
</root>
</configuration>
6.使用效果: