标签搜索

目 录CONTENT

文章目录

架构师之路(二)

WP&CZH
2024-05-16 / 0 评论 / 0 点赞 / 439 阅读 / 1,563 字 / 正在检测是否收录...

架构师之路(二)

今天,我们继续来讲一个平时司空见惯,但其实很有讲究的内容,那就是:接口打日志

打日志可不是随便打打的。日志打得不好,不仅不便于问题排查,甚至会拖垮机器,造成线上故障。

我的第一任老板当时就和我们这些初出茅庐的开发说:看一个人技术好不好,你就看他打日志就行

这么多年工作过来,真是越来越深刻地体会到这句话的正确性。

今天我们就来聊聊“打日志”。怎么聊呢?直接聊很枯燥,我们就结合具体的代码例子来看吧。

package com.czh.controller;

import com.czh.aop.annotation.Anonymous;
import com.czh.aop.annotation.SystemLog;
import com.czh.design.login.LoginContext;
import com.czh.enums.LoginEnum;
import com.czh.service.LoginService;
import com.czh.utils.ip.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @ClassNAME LoginController
 * @Description TODO
 * @Author czh
 * @Date 2024/5/16 09:32
 * @Version 1.0
 */
@RestController
@Anonymous
@Slf4j
public class LoginController {
    @Autowired
    private LoginContext loginContext;

    /**
     * 后台登录
     * @param userName
     * @param password
     */
    @PostMapping("login")
    public void  login(HttpServletRequest request,String userName, String password){
        log.info("=================start================");
        log.info("入口功能:后台登录");
        log.info("IP:" + IpUtils.getIpAddr(request));
        log.info("入口参数=" + userName, password);
        loginContext.login(LoginEnum.ADMIN,userName,password);
        log.info("=================end================");
    }


    /**
     * app登录
     * @param token
     *
     */
    @PostMapping("appLogin")
    public void appLogin(HttpServletRequest request,String token){
        log.info("=================start================");
        log.info("入口功能:后台登录");
        log.info("IP:" + IpUtils.getIpAddr(request));
        log.info("入口参数=" + token);
        loginContext.login(LoginEnum.APP,token);
        log.info("=================end================");
    }

    /**
     * 支付宝登录
     * @param token
     *
     */
    @PostMapping("zfbLogin")
    public void  zfbLogin(String token){

        loginContext.login(LoginEnum.ZFB,token);

    }

}

image-20240516135015584

可以看到日志打印是没有问题的

你觉得这样打印日志ok吗?

问题一:每个接口都要写这种重复的代码
问题二:日志代码影响业务代码的可读性

看了有问题的代码,下一步我们要做的就是改造它!这种日志不相关业务系统我们就可以用到spring Aop的加强机制

直接上代码:

首先定义一个自定义日志注解

package com.czh.aop.annotation;

import java.lang.annotation.*;

/**
 * 自定义操作日志记录注解
 *
 * @author czh
 *
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
    /**
     * 模块
     */
    public String title() default "";

    /**
     * 功能
     */
    public String content() default "";
}

写入切面

package com.czh.aop.aspectj;

import com.alibaba.fastjson.JSON;
import com.czh.aop.annotation.SystemLog;
import com.czh.utils.ip.IpUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.ThreadContext;
import org.apache.tomcat.util.net.IPv6Utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

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

/**
 * 自定义操作日志记录注解
 *
 * @author czh
 */
@Component
@Aspect
public class SystemLogAspect {
    private static final Logger log = LoggerFactory.getLogger(SystemLogAspect.class);
    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    /**
     * 设置操作日志切入点,这里介绍两种方式:
     * 1、基于注解切入(也就是打了自定义注解的方法才会切入)
     *
     * @Pointcut("@annotation(com.czh.aop.annotation.SystemLog)") 2、基于包扫描切入
     * @Pointcut("@annotation(com.czh.aop.annotation.SystemLog)")
     */
    @Pointcut("@annotation(com.czh.aop.annotation.SystemLog)")//在注解的位置切入代码
    //@Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")//从controller切入
    public void operLogPoinCut() {

    }

    /**
     * 处理请求前执行
     */
    @Before("operLogPoinCut()")
    public void boBefore(JoinPoint joinPoint) {

        //为了记录方法的执行时间
        TIME_THREADLOCAL.set(System.currentTimeMillis());
        ThreadContext.put("traceId", UUID.randomUUID().toString());
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取操作
            SystemLog myLog = method.getAnnotation(SystemLog.class);

            ThreadContext.put("traceId", UUID.randomUUID().toString());
            // 将入参转换成json
            String params = argsArrayToString(joinPoint.getArgs());
            log.info("=================start================");
            log.info("入口模块:" + myLog.title());
            log.info("入口功能:" + myLog.content());
            log.info("IP:" + IpUtils.getIpAddr(request));
            log.info("入口参数=" + params);
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();


        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp);
            exp.printStackTrace();
        }

    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(value = "operLogPoinCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
    {
        try {
            // 设置消耗时间
            log.info("消耗时间(ms)" + String.valueOf(System.currentTimeMillis() - TIME_THREADLOCAL.get()));
            log.info("=================end============");
        } finally {
            TIME_THREADLOCAL.remove();
        }
    }

    /**
     * @param: 参数paramsArray
     * @return: String
     * @description:拼接参数
     * @author: czh
     * @date: 2023/12/24 21:16
     **/
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return params.trim();
    }



}

最新的代码使用在需要的方法上面加上注解即可

package com.czh.controller;

import com.czh.aop.annotation.Anonymous;
import com.czh.aop.annotation.SystemLog;
import com.czh.design.login.LoginContext;
import com.czh.enums.LoginEnum;
import com.czh.service.LoginService;
import com.czh.utils.ip.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @ClassNAME LoginController
 * @Description TODO
 * @Author czh
 * @Date 2024/5/16 09:32
 * @Version 1.0
 */
@RestController
@Anonymous
@Slf4j
public class LoginController {
    @Autowired
    private LoginContext loginContext;

    /**
     * 后台登录
     * @param userName
     * @param password
     */
    @PostMapping("login")
    public void  login(HttpServletRequest request,String userName, String password){
        log.info("=================start================");
        log.info("入口功能:后台登录");
        log.info("IP:" + IpUtils.getIpAddr(request));
        log.info("入口参数=" + userName, password);
        loginContext.login(LoginEnum.ADMIN,userName,password);
        log.info("=================end================");
    }


    /**
     * app登录
     * @param token
     *
     */
    @PostMapping("appLogin")
    public void appLogin(HttpServletRequest request,String token){
        log.info("=================start================");
        log.info("入口功能:后台登录");
        log.info("IP:" + IpUtils.getIpAddr(request));
        log.info("入口参数=" + token);
        loginContext.login(LoginEnum.APP,token);
        log.info("=================end================");
    }

    /**
     * 支付宝登录
     * @param token
     *
     */
    @PostMapping("zfbLogin")
    @SystemLog(title = "支付宝登录",content = "支付宝登录")
    public void  zfbLogin(String token){

        loginContext.login(LoginEnum.ZFB,token);

    }

}

image-20240516135637914

好了到此结束了如果对你有用请点一下赞!

0

评论区