Spring Boot——使用AOP統(tǒng)一處理Web請求日志

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.使用效果:

  • 控制臺打印結(jié)果
  • 控制層代碼
  • 數(shù)據(jù)庫存放結(jié)果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亭姥,隨后出現(xiàn)的幾起案子稼钩,更是在濱河造成了極大的恐慌,老刑警劉巖致份,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件变抽,死亡現(xiàn)場離奇詭異础拨,居然都是意外死亡氮块,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門诡宗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滔蝉,“玉大人,你說我怎么就攤上這事塔沃◎鹨” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長螃概。 經(jīng)常有香客問我矫夯,道長,這世上最難降的妖魔是什么吊洼? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任训貌,我火速辦了婚禮,結(jié)果婚禮上冒窍,老公的妹妹穿的比我還像新娘递沪。我一直安慰自己,他們只是感情好综液,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布款慨。 她就那樣靜靜地躺著,像睡著了一般谬莹。 火紅的嫁衣襯著肌膚如雪檩奠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天附帽,我揣著相機與錄音笆凌,去河邊找鬼。 笑死士葫,一個胖子當著我的面吹牛乞而,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播慢显,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爪模,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了荚藻?” 一聲冷哼從身側(cè)響起屋灌,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎应狱,沒想到半個月后共郭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡疾呻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年除嘹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岸蜗。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡尉咕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出璃岳,到底是詐尸還是另有隱情年缎,我是刑警寧澤悔捶,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站单芜,受9級特大地震影響蜕该,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洲鸠,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一蛇损、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坛怪,春花似錦淤齐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至居灯,卻和暖如春祭务,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怪嫌。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工义锥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岩灭。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓拌倍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親噪径。 傳聞我的和親對象是個殘疾皇子柱恤,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355