自学内容网 自学内容网

springboot 前后端处理日志

为了实现一个高效且合理的日志记录方案,我们需要在系统架构层面进行细致规划。在某些情况下,一个前端页面可能会调用多个辅助接口来完成整个业务流程,而并非所有这些接口的交互都需要被记录到日志中。为了避免不必要的日志开销,并确保日志系统的可读性和有效性,我们可以采用以下策略:

日志记录策略概述
前端控制:
前端在发起请求时,可以通过HTTP头(Header)传递一个标志,指示当前请求是否需要记录日志。例如,可以使用一个自定义的Header,如operation-log,其值可以是true或false。
后端响应:
后端服务接收到请求后,首先检查该Header的存在及值。如果该Header存在且其值为true,则后端将记录此次请求的相关信息;反之,则忽略日志记录。
下面贴出核心代码
注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Loggable {
    // 接口信息
    String value() default "";

    // 是否记录响应结果,默认为true
    boolean logResponse() default true;

    // 模块
    String module() default "";

    // 类型 insert 新增 delete 删除  select 查看 update 修改
    String type() default "";

}

配置信息

import cn.com.nmd.base.constant.Constants;
import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TraceIdFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String traceId = IdUtil.objectId();
MDC.put(Constants.TRACE_ID, traceId);
try {
// 响应头中添加 traceId 参数,方便排查问题
response.setHeader(Constants.TRACE_ID, traceId);
filterChain.doFilter(request, response);
}
finally {
MDC.remove(Constants.TRACE_ID);
}
}

}
import cn.com.nmd.auth.log.mdc.TraceIdFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;



@Configuration
@ConditionalOnWebApplication
public class TraceIdAutoConfiguration {

@Bean
public FilterRegistrationBean<TraceIdFilter> traceIdFilterRegistrationBean() {
FilterRegistrationBean<TraceIdFilter> registrationBean = new FilterRegistrationBean<>(new TraceIdFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}

}

工具类

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;


public class WebUtils extends org.springframework.web.util.WebUtils {

/**
 * 获取 ServletRequestAttributes
 * @return {ServletRequestAttributes}
 */
public static ServletRequestAttributes getServletRequestAttributes() {
return (ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes());
}

/**
 * 获取 HttpServletRequest
 * @return {HttpServletRequest}
 */
public static HttpServletRequest getRequest() {
return getServletRequestAttributes().getRequest();
}

/**
 * 获取 HttpServletResponse
 * @return {HttpServletResponse}
 */
public static HttpServletResponse getResponse() {
return getServletRequestAttributes().getResponse();
}

}

拦截器

import cn.com.nmd.auth.annotation.Loggable;
import cn.com.nmd.auth.utils.SecurityUtils;
import cn.com.nmd.auth.utils.WebUtils;
import cn.com.nmd.base.constant.Constants;
import cn.com.nmd.base.model.SysOperationLog;
import cn.com.nmd.base.utils.IpUtils;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

@Aspect
@Component
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    private final List<Class<?>> ignoredParamClasses = ListUtil.toList(ServletRequest.class, ServletResponse.class,
            MultipartFile.class);

    // 定义切入点,匹配带有 @Loggable 注解的方法
    @Pointcut("@annotation(cn.com.nmd.auth.annotation.Loggable)")
    public void loggableMethod() {
    }

    @Around("loggableMethod()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取 Request
        HttpServletRequest request = WebUtils.getRequest();
        String operateLog = request.getHeader("operation-log");
        // 开始时间
        long startTime = System.currentTimeMillis();
        SysOperationLog sysOperationLog = null;
        boolean b = false;

        if ("true".equals(operateLog)) {
            // 获取目标方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Loggable loggable = method.getAnnotation(Loggable.class);
            b = loggable.logResponse();
            sysOperationLog = this.buildLog(loggable, joinPoint);
        }
        Throwable throwable = null;
        Object result = null;
        try {
            result = joinPoint.proceed();
            return result;
        } catch (Throwable e) {
            throwable = e;
            throw e;
        } finally {
            if ("true".equals(operateLog)) {
                // 操作日志记录处理
                this.handleLog(startTime, sysOperationLog, throwable, b, result);
            }
        }
    }

    private void handleLog(long startTime, SysOperationLog sysOperationLog, Throwable throwable, boolean b, Object result) {
        try {
            // 结束时间
            long executionTime = System.currentTimeMillis() - startTime;
            // 执行时长
            sysOperationLog.setOperateTime(executionTime);
            // 执行状态
            String logStatus = throwable == null ? Constants.SUCCESS : Constants.FAIL;
            sysOperationLog.setStatus(logStatus);
            // 执行结果
            if (b) {
                Optional.ofNullable(result).ifPresent(x -> sysOperationLog.setResult(JSON.toJSONString(x)));
            }else {
                sysOperationLog.setResult(("{\"code\": \"200\",\"message\": \"\",\"data\": \"\"}"));
            }
            // 保存操作日志
            logger.info("保存的日志数据:{}", JSON.toJSONString(sysOperationLog));
        } catch (Exception e) {
            logger.error("记录操作日志异常:{}", JSON.toJSONString(sysOperationLog));
        }
    }

    private SysOperationLog buildLog(Loggable loggable, ProceedingJoinPoint joinPoint) {
        HttpServletRequest request = WebUtils.getRequest();
        SysOperationLog operationLog = new SysOperationLog();
        operationLog.setIp(IpUtils.getIpAddr(request));
        operationLog.setMethod(request.getMethod());
        operationLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
        operationLog.setUri(URLUtil.getPath(request.getRequestURI()));
        operationLog.setType(loggable.type());
        operationLog.setModule(loggable.module());
        operationLog.setTraceId(MDC.get(Constants.TRACE_ID));
        operationLog.setCreateTime(new Date());
        operationLog.setParams(getParams(joinPoint));
        operationLog.setCreateUserId(SecurityUtils.getUserId());
        return operationLog;
    }


    /**
     * 获取方法参数
     *
     * @param joinPoint 切点
     * @return 当前方法入参的Json Str
     */
    public String getParams(ProceedingJoinPoint joinPoint) {
        // 获取方法签名
        Signature signature = joinPoint.getSignature();
        String strClassName = joinPoint.getTarget().getClass().getName();
        String strMethodName = signature.getName();
        MethodSignature methodSignature = (MethodSignature) signature;
        logger.debug("[getParams],获取方法参数[类名]:{},[方法]:{}", strClassName, strMethodName);

        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        Method method = ((MethodSignature) signature).getMethod();
        if (ArrayUtil.isEmpty(parameterNames)) {
            return null;
        }
        Map<String, Object> paramsMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            Object arg = args[i];
            if (arg == null) {
                paramsMap.put(parameterNames[i], null);
                continue;
            }
            Class<?> argClass = arg.getClass();
            // 忽略部分类型的参数记录
            for (Class<?> ignoredParamClass : ignoredParamClasses) {
                if (ignoredParamClass.isAssignableFrom(argClass)) {
                    arg = "ignored param type: " + argClass;
                    break;
                }
            }
            paramsMap.put(parameterNames[i], arg);
        }
        // 特别处理 @RequestBody 参数
        for (int i = 0; i < parameterNames.length; i++) {
            Object arg = args[i];
            if (arg != null && method.getParameterAnnotations()[i].length > 0) {
                RequestBody requestBodyAnnotation = method.getParameterAnnotations()[i][0].annotationType().getAnnotation(RequestBody.class);
                if (requestBodyAnnotation != null) {
                    paramsMap.put(parameterNames[i], arg);
                }
            }
        }
        String params = "";
        try {
            // 入参类中的属性可以通过注解进行数据落库脱敏以及忽略等操作
            params = JSON.toJSONString(paramsMap);
        } catch (Exception e) {
            logger.error("[getParams],获取方法参数异常,[类名]:{},[方法]:{}", strClassName, strMethodName, e);
        }

        return params;
    }
}

示例

 @Loggable(value = "保存采集信息", type = "insert", module = "业务管理-场站管理", logResponse = true)
    

原文地址:https://blog.csdn.net/qq_45502554/article/details/142826186

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!