统一接口日志处理(日志入库)

统一接口日志处理(日志入库)

1.环境搭建

日志数据库

--  接口日志信息表
 CREATE TABLE `log_note`  (
  `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT ''主键'',
  `interface_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT ''接口名称'',
  `interface_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''请求地址'',
  `interface_request` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''请求内容'',
  `interface_request_time` bigint(20) DEFAULT NULL COMMENT ''请求时间'',
  `interface_reponse_time` bigint(20) DEFAULT NULL COMMENT ''响应时间'',
  `interface_length_time` bigint(20) DEFAULT NULL COMMENT ''消耗时长(ms)'',
  `interface_reponse` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''响应内容'',
  `interface_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''服务执行地址'',
  `interface_error` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''服务异常信息'',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

日志实体类

package com.chif.goingplus.aop.log.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 日志信息实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LogNote {

    private Long id;
    private String InterfaceName;
    private String InterfaceUrl;
    private String InterfaceRequest;
    private Long InterfaceRequestTime;
    private Long InterfaceReponseTime;
    private Long InterfaceLengthTime;
    private String InterfaceReponse;
    private String InterfaceIp;
    private String InterfaceError;

}

日志记录业务类

package com.chif.goingplus.aop.log.service;

import com.alibaba.fastjson.JSON;
import com.chif.goingplus.aop.log.IPUtil;
import com.chif.goingplus.aop.log.mapper.LogNoteMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author liudachu
 * @description: 日志记录业务类
 * @date 2021/1/18
 */
@Service
@Slf4j
public class LogNoteService {

    @Resource
    public LogNoteMapper logNoteMapper;

    /**
     * 日志插入(后期可做异步处理)
     *
     * @param joinPoint
     * @param data
     * @param logNote
     */
    public void logInsert(JoinPoint joinPoint, Object data, com.chif.goingplus.aop.log.pojo.LogNote logNote) {
        logNote.setInterfaceUrl(getUrl(joinPoint));
        logNote.setInterfaceRequest(getRequestContent(joinPoint.getArgs()));
        logNote.setInterfaceReponseTime(System.currentTimeMillis());
        logNote.setInterfaceReponse(data == null ? "" : JSON.toJSONString(data));
        logNote.setInterfaceLengthTime(logNote.getInterfaceReponseTime() - logNote.getInterfaceRequestTime());
        logNote.setInterfaceIp(IPUtil.getLocalIP());
        logNoteMapper.insertLogNote(logNote);
    }

    /**
     * 获取访问地址
     *
     * @param joinPoint
     * @return
     */
    private String getUrl(JoinPoint joinPoint) {
        StringBuffer url = new StringBuffer();
        Method methodSignature = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object target = joinPoint.getTarget();
        methodSignature.getParameters();
        RequestMapping requestMapping = target.getClass().getAnnotation(RequestMapping.class);
        if (requestMapping != null && requestMapping.value().length > 0) {
            url.append(requestMapping.value()[0]);
        }
        PostMapping postMapping = methodSignature.getAnnotation(PostMapping.class);
        if (postMapping != null && postMapping.value().length > 0) {
            url.append(postMapping.value()[0]);
        }
        GetMapping getMapping = methodSignature.getAnnotation(GetMapping.class);
        if (getMapping != null && getMapping.value().length > 0) {
            url.append(getMapping.value()[0]);
        }
        RequestMapping request = methodSignature.getAnnotation(RequestMapping.class);
        if (request != null && request.value().length > 0) {
            url.append(request.value()[0]);
        }
        DeleteMapping deleteMapping = methodSignature.getAnnotation(DeleteMapping.class);
        if (deleteMapping != null && deleteMapping.value().length > 0) {
            url.append(deleteMapping.value()[0]);
        }
        PutMapping putMapping = methodSignature.getAnnotation(PutMapping.class);
        if (putMapping != null && putMapping.value().length > 0) {
            url.append(putMapping.value()[0]);
        }
        return url.toString();
    }

    /**
     * 获取请求内容
     *
     * @param objects
     * @return
     */
    private String getRequestContent(Object[] objects) {
        StringBuffer stringBuffer = new StringBuffer();
        AtomicInteger i = new AtomicInteger(1);
        Arrays.stream(objects).forEach(item -> {
            if (!Objects.isNull(item) && objects.length > 1) {
                stringBuffer.append("参数" + (i.getAndIncrement()) + ":");
            }
            stringBuffer.append(JSON.toJSONString(item));
        });
        return stringBuffer.toString();
    }

}

开启AOP配置

package com.chif.goingplus.aop;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
// 开启AOP
@EnableAspectJAutoProxy
public class AOPConfig {
}

2. 自定义@Log注解

package com.chif.goingplus.aop.log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义日志注解(用于方法上)
 * @author liudachu
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface LogNote {
    String value() default "";
}

3.定义切面

package com.chif.goingplus.aop.log;

import com.chif.goingplus.aop.log.service.LogNoteService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 统一接口日志处理(日志入库)
 * @author liudachu
 */
@Aspect
@Component
public class LogAspect {

    /**
     * 日志集合,用于存储不同线程下的LogNote(日志记录)
     */
    private static Map<String, com.chif.goingplus.aop.log.pojo.LogNote> logNoteMap = new ConcurrentHashMap<>();

    @Resource
    LogNoteService logNoteService;

    /**
     * 定义切点,切所有的Controller中的接口方法
     */
    @Pointcut("execution(public * com.chif.goingplus.controller.*.*(..))")
    public void LogAspect() {
    }

    /**
     * 在目标方法完全执行后(return后)再执行
     * @param joinPoint
     * @param data
     */
    @AfterReturning(value = "LogAspect()", returning = "data")
    public void doAfterReturning(JoinPoint joinPoint, Object data) {
        logNote(joinPoint, data);
    }

    /**
     * 包裹目标方法
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("LogAspect()")
    public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Method methodSignature = ((MethodSignature) joinPoint.getSignature()).getMethod();
        LogNote loggerInfo = methodSignature.getAnnotation(LogNote.class);
        if (loggerInfo != null) {
            //记录接口执行信息
            Thread.currentThread().setName(UUID.randomUUID().toString());
            com.chif.goingplus.aop.log.pojo.LogNote logNote = new com.chif.goingplus.aop.log.pojo.LogNote();
            logNote.setInterfaceRequestTime(System.currentTimeMillis());
            logNote.setInterfaceName(loggerInfo.value());
            logNoteMap.put(Thread.currentThread().getName(), logNote);
        }
        Object obj;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            //持久化异常日志
            logNote(joinPoint, e);
            e.printStackTrace();
            throw e;
        }
        return obj;
    }


    /**
     * 日志记录(持久化)
     *
     * @param joinPoint
     * @param data
     * @param e
     */
    private void logNote(JoinPoint joinPoint, Object data, Throwable e) {
        if (!logNoteMap.containsKey(Thread.currentThread().getName())) {
            //当前线程无日志记录,结束执行该持久化操作
            return;
        }
        //持久化日志到数据库,内存(map集合)中剔除该日志
        com.chif.goingplus.aop.log.pojo.LogNote logNote = logNoteMap.get(Thread.currentThread().getName());
        logNote.setInterfaceError(e == null ? "" : getErrorMsg(e));
        logNoteMap.remove(Thread.currentThread().getName());
        logNoteService.logInsert(joinPoint, data, logNote);

    }

    /**
     * 获取异常信息打印日志
     * 格式化
     * @param e
     * @return
     */
    private String getErrorMsg(Throwable e) {
        if (e == null) {
            return "";
        }
        StackTraceElement[] trace = e.getStackTrace();
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(e);
        for (int i = 0; i < 8; i++) {
            stringBuffer.append("\r\n at " + trace[i]);
        }
        return stringBuffer.toString();
    }

    /**
     * 持久化接口异常日志
     * @param joinPoint
     * @param e
     */
    private void logNote(JoinPoint joinPoint, Throwable e) {
        logNote(joinPoint, null, e);
    }

    /**
     * 持久化接口运行日志
     * @param joinPoint
     * @param data
     */
    private void logNote(JoinPoint joinPoint, Object data) {
        logNote(joinPoint, data, null);
    }

}

4.测试

统一接口日志处理(日志入库)

5.最终实现效果

统一接口日志处理(日志入库)

上一篇:Tomcat启动会遇到的问题部分解决方案


下一篇:TPL异步并行编程之简单使用