java项目增加统一切面日志输出,对于项目调试问题排查还是很有必要的

废话不多说,先上效果

在这里插入图片描述

相关代码

1.目录结构

在这里插入图片描述

2.核心切面类LogAspect

package com.zwd.cases.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import org.aspectj.lang.JoinPoint;
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.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.UUID;

/**
 * @Description 统一切面日志输出
 * @Author zhengwd
 * @Date 2023/4/11 10:36
 **/
@Aspect
@Component
public class LogAspect {

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

    /**
     * 定义一个切点
     */
    @Pointcut("execution(public * com.zwd.*.controller..*Controller.*(..))")
    public void controllerPointcut() {}

    @Before("controllerPointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 日志编号
        MDC.put("UUID", UUID.randomUUID() + "");

        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();

        // 打印业务操作
        String nameCn = "";
        if (name.contains("list") || name.contains("query")) {
            nameCn = "查询";
        } else if (name.contains("save")) {
            nameCn = "保存";
        } else if (name.contains("delete")) {
            nameCn = "删除";
        } else {
            nameCn = "操作";
        }

        // 使用反射,获取业务名称
        Class clazz = signature.getDeclaringType();
        Field field;
        String businessName = "";
        try {
            field = clazz.getField("BUSINESS_NAME");
            if (!StringUtils.isEmpty(field)) {
                businessName = (String) field.get(clazz);
            }
        } catch (NoSuchFieldException e) {
            LOG.error("未获取到业务名称");
        } catch (SecurityException e) {
            LOG.error("获取业务名称失败", e);
        }

        // 打印请求信息
        LOG.info("------------- 【{}】{}开始 -------------", businessName, nameCn);
        LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
        LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
        LOG.info("远程地址: {}", request.getRemoteAddr());

        // 打印请求参数
        Object[] args = joinPoint.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest
                    || args[i] instanceof ServletResponse
                    || args[i] instanceof MultipartFile) {
                continue;
            }
            arguments[i] = args[i];
        }
        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {"shard"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);
        // 为空的会不打印,但是像图片等长字段也会打印
        LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter));
    }

    @Around("controllerPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {"password", "shard"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);
        LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));
        LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
        return result;
    }

}

注意:定义的切点目录需要修改成你项目对应的位置。否则会不生效。
在这里插入图片描述

3.UploadController

package com.zwd.cases.controller;

import com.zwd.cases.common.constant.ResponseDto;
import com.zwd.cases.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Description 文件上传
 * @Author zhengwd
 * @Date 2022/7/21 16:30
 **/
@Slf4j
@Controller
@RequestMapping("testup")
public class UploadController {

    public static final String BUSINESS_NAME = "文件上传";

    /**
     * 单个文件上传页面
     * @return
     */
    @RequestMapping("/list")
    public ResponseDto list(User user) {
        ResponseDto responseDto = new ResponseDto();
        responseDto.setContent(user);
        return responseDto;
    }
}

此处的 BUSINESS_NAME 属性不能删了,LogAspect类中有使用!!!

4.ResponseDto

package com.zwd.cases.common.constant;

public class ResponseDto<T> {

    /**
     * 业务上的成功或失败
     */
    private boolean success = true;

    /**
     * 返回码
     */
    private String code;

    /**
     * 返回信息
     */
    private String message;

    /**
     * 返回泛型数据,自定义类型
     */
    private T content;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public boolean getSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("ResponseDto{");
        sb.append("success=").append(success);
        sb.append(", code='").append(code).append('\'');
        sb.append(", message='").append(message).append('\'');
        sb.append(", content=").append(content);
        sb.append('}');
        return sb.toString();
    }
}

5.User

package com.zwd.cases.domain;

import lombok.Data;

/**
 * @Description 用户对象
 * @Author zhengwd
 * @Date 2023/4/11 14:55
 **/
@Data
public class User {
    private String name;
    private String age;
}

6.pom.xml文件如果没有AOP切面相关的依赖的话,需要添加以下依赖

		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.2.9.RELEASE</version>
        </dependency>

fastjson依赖,看自己原先项目有没有了,有就不用加

        <dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.79</version>
		</dependency>

请求地址:http://localhost:8080/testup/list?name=zwd&age=1

查看控制台效果:
在这里插入图片描述

扩展(以下是人工智能的回复)

问:面向切面编程能做些什么?简单举例下

答:
// 面向切面编程(AOP)能够在不修改原有代码的情况下,通过切面(Aspect)来实现对代码的增强或者横切关注点的分离。常见的应用场景包括日志记录、性能统计、安全控制、事务处理等。下面是一个简单的例子,使用AOP实现对方法执行时间的统计:


// 定义一个切面类,使用@Aspect注解标注
@Aspect
public class TimeAspect {
    // 定义一个切点,匹配com.example.demo包及其子包下的所有类的所有方法
    @Pointcut("execution(* com.example.demo..*.*(..))")
    public void pointcut() {}

    // 定义一个前置通知,在目标方法执行前输出开始时间
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("开始执行:" + joinPoint.getSignature() + ",参数:" + Arrays.toString(joinPoint.getArgs()));
        joinPoint.getArgs();
    }

    // 定义一个后置通知,在目标方法执行后输出结束时间和执行时间
    @AfterReturning(pointcut = "pointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("执行完成:" + joinPoint.getSignature() + ",返回值:" + result);
    }

    // 定义一个异常通知,在目标方法抛出异常时输出异常信息
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        System.out.println("执行出错:" + joinPoint.getSignature() + ",异常信息:" + e.getMessage());
    }
}

// 在Spring配置文件中开启AspectJ自动代理,并扫描TimeAspect所在的包
<aop:aspectj-autoproxy />
<context:component-scan base-package="com.example.demo.aspect" />

// 在需要统计执行时间的方法上添加@Aspect注解
@Service
public class UserServiceImpl implements UserService {
    @Override
    @TimeAspect
    public void addUser(User user) {
        // ...
    }
}

完结,撒花~~~

附相关其他日志:idea控制台显示日志规范化并存储控制台输出日志操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值