1. 程式人生 > 其它 >個人部落格開發之blog-api 專案全域性日誌攔截記錄

個人部落格開發之blog-api 專案全域性日誌攔截記錄

前言

大型完善專案中肯定是需要一個全域性日誌攔截,記錄每次介面訪問相關資訊,包括:
訪問ip,訪問裝置,請求引數,響應結果,響應時間,開始請求時間,訪問介面描述,訪問的使用者,介面地址,請求型別,便於專案的除錯追蹤

整合日誌

SpringBoot已經幫我們做了日誌整合,在它的父pom項中

     <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

我們點進去看到還有父pom項

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.4.7</version>
  </parent>

spring-boot-dependencies幫我自動整合所有jar包版本,和內建很多啟動項start,這是SpringBoot使用起來不需要配置那麼多jar包依賴關係的本質,點進去我們看到了,所有spring,springmvc所需要依賴,和jar版本


最終我們找到了日誌依賴所需jar包,所以我們直接使用就行,不需要額外引入

配置日誌

######日誌配置######
# 日誌檔案
logging.config=classpath:logback-spring.xml
#日誌包級別
#logging.level.root=info
logging.level.cn.soboys.blogapi=info
#日誌儲存路徑
logging.file.path=/Users/xiangyong/selfProject/project/blog-api/log 

日誌檔案配置

這裡日誌名字logback-spring.xml是SpringBoot獨有,這麼寫spring boot可以為它新增一些spring boot特有的配置項

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>shop-api</contextName>
    <!--定義日誌檔案的儲存地址 從springboot配置檔案中獲取路徑-->
    <springProperty scope="context" name="LOG_PATH" source="logging.file.path"/>
    <!--springboot配置檔案中獲取日誌級別-->
    <springProperty scope="context" name="LOG_LEVEL" source="logging.level.root"/>
   <!-- <property name="log.path" value="log" />-->
    <property name="log.maxHistory" value="15" />
    <property name="log.colorPattern" value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %yellow(%thread) %green(%logger) %msg%n"/>
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %thread %logger %msg%n"/>

    <!--輸出到控制檯-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.colorPattern}</pattern>
        </encoder>
    </appender>

    <!--輸出到檔案-->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <MaxHistory>${log.maxHistory}</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/error/error.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <root level="debug">
        <appender-ref ref="console" />
    </root>

    <root level="info">
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
    </root>
</configuration>

這裡我把日誌分成兩個日誌檔案,一個錯誤日誌,一個資訊日誌,按照明天一個日誌檔案方式

全域性日誌記錄

這裡我們基於aop切面進行請求攔截,記錄所有請求相關資訊

package cn.soboys.core;

import lombok.Data;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 18:48
 * 日誌資訊
 */
@Data
public class LogSubject {
    /**
     * 操作描述
     */
    private String description;

    /**
     * 操作使用者
     */
    private String username;

    /**
     * 操作時間
     */
    private String startTime;

    /**
     * 消耗時間
     */
    private String spendTime;

    /**
     * URL
     */
    private String url;

    /**
     * 請求型別
     */
    private String method;

    /**
     * IP地址
     */
    private String ip;

    /**
     * 請求引數
     */
    private Object parameter;

    /**
     * 請求返回的結果
     */
    private Object result;

    /**
     * 城市
     */
    private String city;

    /**
     * 請求裝置資訊
     */
    private String device;



}

這裡用到了aop 所以要匯入aop相關包

 <!--aop 切面-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

編寫切面類攔截對應的controller請求

package cn.soboys.core;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 14:52
 * 切面
 */
public class BaseAspectSupport {
    public Method resolveMethod(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();

        Method method = getDeclaredMethod(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("無法解析目標方法: " + signature.getMethod().getName());
        }
        return method;
    }

    private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethod(superClass, name, parameterTypes);
            }
        }
        return null;
    }
}

日誌記錄切面GlobalLogAspect

package cn.soboys.core;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 15:22
 * 全域性日誌記錄器
 */
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
    /**
     * 定義切面Pointcut
     */
    @Pointcut("execution(public * cn.soboys.blogapi.controller.*.*(..))")
    public void log() {

    }


    /**
     * 環繞通知
     *
     * @param joinPoint
     * @return
     */
    @Around("log()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        LogSubject logSubject = new LogSubject();
        //記錄時間定時器
        TimeInterval timer = DateUtil.timer(true);
        //執行結果
        Object result = joinPoint.proceed();
        logSubject.setResult(result);
        //執行消耗時間
        String endTime = timer.intervalPretty();
        logSubject.setSpendTime(endTime);
        //執行引數
        Method method = resolveMethod(joinPoint);
        logSubject.setParameter(getParameter(method, joinPoint.getArgs()));

        HttpServletRequest request = HttpContextUtil.getRequest();
        // 介面請求時間
        logSubject.setStartTime(DateUtil.now());
        //請求連結
        logSubject.setUrl(request.getRequestURL().toString());
        //請求方法GET,POST等
        logSubject.setMethod(request.getMethod());
        //請求裝置資訊
        logSubject.setDevice(HttpContextUtil.getDevice());
        //請求地址
        logSubject.setIp(HttpContextUtil.getIpAddr());
        //介面描述
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            logSubject.setDescription(apiOperation.value());
        }

        String a = JSONUtil.toJsonPrettyStr(logSubject);
        log.info(a);
        return result;

    }

    /**
     * 根據方法和傳入的引數獲取請求引數
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            //將RequestBody註解修飾的引數作為請求引數
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            //將RequestParam註解修飾的引數作為請求引數
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            String key = parameters[i].getName();
            if (requestBody != null) {
                argList.add(args[i]);
            } else if (requestParam != null) {
                map.put(key, args[i]);
            } else {
                map.put(key, args[i]);
            }
        }
        if (map.size() > 0) {
            argList.add(map);
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
}
package cn.soboys.core;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 15:22
 * 全域性日誌記錄器
 */
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
    /**
     * 定義切面Pointcut
     */
    @Pointcut("execution(public * cn.soboys.blogapi.controller.*.*(..))")
    public void log() {

    }


    /**
     * 環繞通知
     *
     * @param joinPoint
     * @return
     */
    @Around("log()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        LogSubject logSubject = new LogSubject();
        //記錄時間定時器
        TimeInterval timer = DateUtil.timer(true);
        //執行結果
        Object result = joinPoint.proceed();
        logSubject.setResult(result);
        //執行消耗時間
        String endTime = timer.intervalPretty();
        logSubject.setSpendTime(endTime);
        //執行引數
        Method method = resolveMethod(joinPoint);
        logSubject.setParameter(getParameter(method, joinPoint.getArgs()));

        HttpServletRequest request = HttpContextUtil.getRequest();
        // 介面請求時間
        logSubject.setStartTime(DateUtil.now());
        //請求連結
        logSubject.setUrl(request.getRequestURL().toString());
        //請求方法GET,POST等
        logSubject.setMethod(request.getMethod());
        //請求裝置資訊
        logSubject.setDevice(HttpContextUtil.getDevice());
        //請求地址
        logSubject.setIp(HttpContextUtil.getIpAddr());
        //介面描述
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            logSubject.setDescription(apiOperation.value());
        }

        String a = JSONUtil.toJsonPrettyStr(logSubject);
        log.info(a);
        return result;

    }

    /**
     * 根據方法和傳入的引數獲取請求引數
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            //將RequestBody註解修飾的引數作為請求引數
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            //將RequestParam註解修飾的引數作為請求引數
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            String key = parameters[i].getName();
            if (requestBody != null) {
                argList.add(args[i]);
            } else if (requestParam != null) {
                map.put(key, args[i]);
            } else {
                map.put(key, args[i]);
            }
        }
        if (map.size() > 0) {
            argList.add(map);
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
}

這樣我們在每次請求介面的時候都會記錄請求的資訊:

詳細日誌學習SpringBoot日誌使用請參考我這兩篇文章

SpringBoot日誌詳細使用和配置

JAVA 中日誌的記錄於使用

關注公眾號猿小叔獲取更多幹貨分享