系统日志 springboot实现AOP系统内环绕日志记录 最佳实践 已踩过多坑 优化传参收集方式 注解同步实现 记录响应时间 可查询优化接口数据

springboot 项目实现系统日志的最佳实践

效果:

数据库展示
数据库展示
 

源码:

1.表DDL语句

CREATE TABLE `sys_sys_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `user_role` varchar(100) DEFAULT NULL COMMENT '用户角色',
  `user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
  `uri` varchar(100) DEFAULT NULL,
  `operation` varchar(100) DEFAULT NULL,
  `exception_code` int(10) DEFAULT NULL,
  `exception_detail` text,
  `run_time` bigint(15) DEFAULT NULL,
  `method` varchar(100) DEFAULT NULL,
  `params` text,
  `ip` varchar(20) DEFAULT NULL,
  `status` tinyint(1) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=519 DEFAULT CHARSET=utf8mb4;

2.entity

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

/**
 * 系统日志
 * @author cc
 * @date 2020/06/05
 */
@Data
@Table(name = "sys_sys_log")
@ApiModel(value = "系统日志")
public class SysLog {
    /**
     * ID
     */
    @Id
    @KeySql(useGeneratedKeys = true)
    @ApiModelProperty(value = "ID")
    private Long id;
    /**
     * 用户ID
     */
    @ApiModelProperty(value = "用户ID")
    private Long userId;
    /**
     * 用户角色
     */
    @ApiModelProperty(value = "用户角色")
    private String userRole;
    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    private String userName;
    /**
     * 请求URI
     */
    @ApiModelProperty(value = "请求URI")
    private String uri;
    /**
     * 动作
     */
    @ApiModelProperty(value = "动作")
    private String operation;
    /**
     * 异常码
     */
    @ApiModelProperty(value = "异常码")
    private Integer exceptionCode;
    /**
     * 异常详情
     */
    @ApiModelProperty(value = "异常详情")
    private String exceptionDetail;
    /**
     * 运行时长(ms)
     */
    @ApiModelProperty(value = "运行时长(ms)")
    private Long runTime;
    /**
     * 方法
     */
    @ApiModelProperty(value = "方法")
    private String method;
    /**
     * 请求参数
     */
    @ApiModelProperty(value = "请求参数")
    private String params;
    /**
     * 请求ip
     */
    @ApiModelProperty(value = "请求ip")
    private String ip;
    /**
     * 状态
     * 0失败 1成功
     */
    @ApiModelProperty(value = "状态(0失败1成功)")
    private Integer status;
    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    private Date createTime;
}

3.注解定义

/**
 * MyLog
 * 自定义aop环绕系统日志
 * @author cc
 * @date 2020/06/05
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {
    /**
     * 用户操作哪个模块
     */
    String title() default "";

    /**
     * 记录用户操作的动作
     */
    String action() default "";
}

4.切面实现

import com.alibaba.fastjson.JSONObject;
import com.cc672cc.aop.annotation.MyLog;
import com.cc672cc.common.model.Payload;
import com.cc672cc.common.model.UserInfo;
import com.cc672cc.common.utils.*;
import com.cc672cc.entity.sys.SysLog;
import com.cc672cc.exceptions.BusinessException;
import com.cc672cc.mapper.SysLogMapper;
import com.cc672cc.properties.JwtProperties;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

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


/**
 * SysLogAspect
 * SysLog切面实现
 *
 * @author cc
 * @date 2020/06/05
 */
@Aspect
@Component
@Slf4j
@SuppressWarnings("all")
public class SysLogAspect {

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private SysLogMapper sysLogMapper;


    private HttpServletRequest request;

    /**
     * 配置织入点(以@MyLog注解为标志)
     * 只要出现 @MyLog注解都会进入
     */
    @Pointcut("@annotation(com.cc672cc.aop.annotation.MyLog)")
    public void logPointCut() {

    }

    /**
     * 环绕增强
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        Exception exception = null;
        long beginTime = 0L;
        long runTime = 0L;
        try {
            beginTime = System.currentTimeMillis();
            result = point.proceed();
            runTime = System.currentTimeMillis() - beginTime;
            saveSuccessSysLog(point, runTime);
        } catch (Exception e) {
            saveErrorSysLog(point, e);
            exception = e;
            log.error("MyLog Exception-->{}", e);
        } finally {
            // 这里要抛出异常给全局异常处理器处理
            if (exception != null) {
                throw exception;
            }
        }

        return result;
    }


    /**
     * 失败日志保存
     *
     * @param joinPoint
     * @param e
     */
    private void saveErrorSysLog(ProceedingJoinPoint joinPoint, Exception e) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLog sysLog = new SysLog();
        MyLog myLog = method.getAnnotation(MyLog.class);
        if (myLog != null) {
            // 注解上的描述
            sysLog.setOperation(myLog.title() + "-" + myLog.action());
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        // 请求的参数
        LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = paramNames.getParameterNames(method);
        //获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();

        // 将参数名称与入参值一一对应起来
        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            // 如果参数类型是请求和响应的http,则不需要拼接【这两个参数,使用JSON.toJSONString()转换会抛异常】
            if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse)
            {
                continue;
            }
            params.put(parameterNames[i], args[i]);
        }

        try {
            sysLog.setParams(JSONObject.toJSONString(params));
        } catch (Exception ex) {
            log.error("MyLog methodParams JSON Exception --> {}",ex);
            sysLog.setParams("MyLog methodParams JSON Exception");
        }
        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        sysLog.setUri(request.getRequestURI());
        // 设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        // 用户名
        String token = request.getHeader("CC_TOKEN");
        Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);
        UserInfo userInfo = payload.getUserInfo();
        sysLog.setUserName(userInfo.getUsername());
        sysLog.setUserRole(userInfo.getRole());
        sysLog.setUserId(userInfo.getId());
        sysLog.setRunTime(-1L);
        sysLog.setCreateTime(new Date());
        sysLog.setStatus(0);
        // 这里做异常处理
        if (e instanceof BusinessException) {
            BusinessException be = (BusinessException) e;
            sysLog.setExceptionCode(be.getCode());
            sysLog.setExceptionDetail(be.getMessage());
        } else {
            sysLog.setExceptionCode(5000000);
            sysLog.setExceptionDetail(e.getMessage());
        }
        sysLog.setId(null);
        sysLogMapper.insertSelective(sysLog);
        LogUtil.logPrint(sysLog);
    }

    /**
     * 成功日志保存
     *
     * @param joinPoint
     * @param time
     */
    private void saveSuccessSysLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysLog sysLog = new SysLog();
        MyLog myLog = method.getAnnotation(MyLog.class);
        if (myLog != null) {
            // 注解上的描述
            sysLog.setOperation(myLog.title() + "-" + myLog.action());
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        // 请求的参数
        LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = paramNames.getParameterNames(method);
        //获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();
        // 将参数名称与入参值一一对应起来
        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            // 如果参数类型是请求和响应的http,则不需要拼接【这两个参数,使用JSON.toJSONString()转换会抛异常】
            if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse)
            {
                continue;
            }
            params.put(parameterNames[i], args[i]);
        }
        try {
            sysLog.setParams(JSONObject.toJSONString(params));
        } catch (Exception ex) {
            log.error("MyLog methodParams JSON Exception --> {}",ex);
            sysLog.setParams("MyLog methodParams JSON Exception");
        }
        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        sysLog.setUri(request.getRequestURI());
        // 设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        // 用户名
        String token = request.getHeader("CC_TOKEN");
        Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey(), UserInfo.class);
        UserInfo userInfo = payload.getUserInfo();
        sysLog.setUserName(userInfo.getUsername());
        sysLog.setUserRole(userInfo.getRole());
        sysLog.setUserId(userInfo.getId());
        sysLog.setRunTime(time);
        sysLog.setCreateTime(new Date());
        sysLog.setStatus(1);
        sysLog.setId(null);
        sysLogMapper.insertSelective(sysLog);
        LogUtil.logPrint(sysLog);
    }
}

6.工具类

6.1 IPUtil

import com.alibaba.druid.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

/**
 * IP地址
 *
 */
@Slf4j
public class IPUtils {


	/**
	 * 获取IP地址
	 * 
	 * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
	 * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
	 */
	public static String getIpAddr(HttpServletRequest request) {
		String ip = null;
		try {
			ip = request.getHeader("x-forwarded-for");
			if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getHeader("Proxy-Client-IP");
			}
			if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getHeader("WL-Proxy-Client-IP");
			}
			if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getHeader("HTTP_CLIENT_IP");
			}
			if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getHeader("HTTP_X_FORWARDED_FOR");
			}
			if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getRemoteAddr();
			}
		} catch (Exception e) {
			log.error("IPUtils ERROR ", e);
		}

		// 使用代理,则获取第一个IP地址
		if (StringUtils.isEmpty(ip) && ip.length() > 15) {
			if (ip.indexOf(",") > 0) {
				ip = ip.substring(0, ip.indexOf(","));
			}
		}

		return ip;
	}

}

6.2 LogUtil

import com.cc672cc.entity.sys.SysLog;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;

/**
 * LogUtil
 * 日志工具类,用于日志统一打印,日志打印要自行异常捕获,防止中止
 * @author cc
 * @date 2020/06/07
 */
@Slf4j
public class LogUtil {
    /**
     * 系统日志打印
     *
     * @param sysLog
     */
    public static void logPrint(SysLog sysLog) {
        log.info("------sysLog-print-start------");
        try {
            log.info("**** sysLog id-> {} ****",sysLog.getId());
            log.info("**** sysLog user-> {} ****",sysLog.getUserName()+"("+sysLog.getUserId()+")");
            log.info("**** sysLog role-> {} ****",sysLog.getUserRole());
            log.info("**** sysLog createTime-> {} ****", DateUtil.format2TimeStr(sysLog.getCreateTime()));
            log.info("**** sysLog uri-> {} ****",sysLog.getUri());
            log.info("**** sysLog rumTime-> {} ms ****",sysLog.getRunTime());
            String status="";
            log.info("**** sysLog status-> {} ****",status=sysLog.getStatus()==1?"成功":"异常");
            if(sysLog.getStatus()==0){
                log.info("**** sysLog exceptionCode-> {} ****",sysLog.getExceptionCode());
                log.info("**** sysLog exceptionMsg-> {} ****",sysLog.getExceptionDetail());
            }
        } catch (Exception e) {
            log.error("sysLog print Exception -->{}",e);
        }finally {
            log.info("------sysLog-print-end------");
        }
    }

    /**
     *
     * @param request
     */
    public static void logPrint(HttpServletRequest request) {
        log.info("------request-intercept-start------");
        try {
            log.info("###### 请求IP: {} ######",IPUtils.getIpAddr(request));
            log.info("###### 请求方式: {} ######",request.getMethod());
            log.info("###### 请求uri: {} ######",request.getRequestURI());
        } catch (Exception e) {
            log.error("request intercept print Exception -->{}",e);
        }finally {
            log.info("------request-intercept-end------");
        }
    }
}

7.controller 实现

@GetMapping("/user/{id}")
    @ApiOperation(value = "根据id获取user")
    @RequiresPermissions("user:view")
    @MyLog(title = "用户管理", action = "获取用户")
    public UserRespVO queryNameById(@ApiParam(value = "用户ID", required = true) @PathVariable("id") int id) {
        return BeanHelper.copyProperties(userService.queryById((long) id), UserRespVO.class);
    }

8. 日志打印展示

标题
标题

9.说明

记录一些关键性的字段,运行时长,这样我们可以在所有标的字段删去更新人字段,也可以做到一个有效的查看接口响应时长来对接口改造优化处理做了一个参考,在实现切面时正规应该将里面的方法同try-catch 块包起来,同步方法不要影响接口响应,那么在此展示中我们对实现异常或接口抛出异常做了封装处理,注意代码中注释的地方,尤其是传参处理的那些坑还是挺多的,和fastjson工具类本身可能抛出的异常,要对这块进行捕获异常处理。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若光672

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值