美文网首页
个人博客开发之blog-api 项目全局日志拦截记录

个人博客开发之blog-api 项目全局日志拦截记录

作者: 程序员三时 | 来源:发表于2021-07-20 09:35 被阅读0次

    前言

    大型完善项目中肯定是需要一个全局日志拦截,记录每次接口访问相关信息,包括:
    访问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版本

    image image

    最终我们找到了日志依赖所需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>
    

    这里我把日志分成两个日志文件,一个错误日志,一个信息日志,按照明天一个日志文件方式

    image

    全局日志记录

    这里我们基于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;
            }
        }
    }
    

    这样我们在每次请求接口的时候都会记录请求的信息:

    image

    详细日志学习SpringBoot日志使用请参考我这两篇文章

    SpringBoot日志详细使用和配置

    JAVA 中日志的记录于使用

    相关文章

      网友评论

          本文标题:个人博客开发之blog-api 项目全局日志拦截记录

          本文链接:https://www.haomeiwen.com/subject/qlslmltx.html