SpringBoot 统计更多Api接口日志信息

第1步:基本配置了解

Further Reading : SpringBoot 统计API接口用时该使用过滤器还是拦截器?

第2步:丰富LogInterceptor(主体流程)

日志打印放afterCompletion是为了兼容异常场景也可以记录日志

import com.zhangziwa.practisesvr.utils.log.LogContext;
import com.zhangziwa.practisesvr.utils.log.ThreadMXBeanUtils;
import com.zhangziwa.practisesvr.utils.log.logUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.time.Instant;

@Component
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.err.println("***LogInterceptor.preHandle***");

        LogContext.setTraceId(logUtils.genUUID());
        LogContext.initSqlCost();
        LogContext.initSqlCost();
        if (Boolean.TRUE.equals(ThreadMXBeanUtils.isThreadCpuTimeEnabled())) {
            LogContext.initCurrentThreadTime();
            LogContext.initCurrentThreadUserTime();
        }
        if (Boolean.TRUE.equals(ThreadMXBeanUtils.isThreadAllocatedMemoryEnabled())) {
            LogContext.initAllocatedBytes();
        }

        long startTime = Instant.now().toEpochMilli();
        request.setAttribute("startTime", startTime);

        log.warn("LogInterceptor.postHandle: Start processing request at {} - {}", Instant.now(), request.getRequestURI());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.err.println("***LogInterceptor.postHandle***");
        // 获取请求开始时间
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.err.println("***LogInterceptor.afterCompletion***");

        // 获取请求开始时间
        Long startTime = (Long) request.getAttribute("startTime");
        long executionCost = 0L;
        if (startTime != null) {
            executionCost = Instant.now().toEpochMilli() - startTime;
            int statusCode = response.getStatus();
            log.warn("LogInterceptor.postHandle: Finished processing request at {} - {} in {} ms. Status code: {}", Instant.now(), request.getRequestURI(), executionCost, statusCode);
        }

        String apiJson = logUtils.buildApiJsonLog(request, response, executionCost);
        log.info(apiJson);
        LogContext.clear();
    }
}

第3步:细枝末节功能介绍

3.1、引入LogContext收纳上下文数据

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class LogContext {
    private static ThreadLocal<AtomicInteger> sql_count = new InheritableThreadLocal<>();
    private static ThreadLocal<AtomicLong> sql_cost = new InheritableThreadLocal<>();
    private static ThreadLocal<String> traceid = new InheritableThreadLocal<>();
    private static ThreadLocal<Long> acclocated_memory = new InheritableThreadLocal<>();
    private static ThreadLocal<Long> total_cpu_cost = new InheritableThreadLocal<>();
    private static ThreadLocal<Long> user_cpu_cost = new InheritableThreadLocal<>();

    public static void initSqlCount() {
        sql_count.set(new AtomicInteger(0));
    }

    public static void incrementSqlCount() {
        if (sql_count.get() == null) {
            sql_count.set(new AtomicInteger(0));
        }
        sql_count.get().incrementAndGet();
    }

    public static int getSqlCount() {
        return sql_count.get().intValue();
    }

    public static void initSqlCost() {
        sql_cost.set(new AtomicLong(0));
    }

    public static void incrementSqlCost(Long sqlCost) {
        if (sql_cost.get() == null) {
            sql_cost.set(new AtomicLong(0));
        }
        sql_cost.get().addAndGet(sqlCost);
    }

    public static Long getSqlCost() {
        return sql_cost.get().longValue();
    }

    public static void setTraceId(String traceId) {
        if (traceid.get() == null) {
            traceid.set(traceId);
        }
    }

    public static String getTraceId() {
        return traceid.get();
    }

    public static void initAllocatedBytes() {
        acclocated_memory.set(ThreadMXBeanUtils.getCurrentThreadAllocatedBytes());
    }

    public static void initCurrentThreadTime() {
        total_cpu_cost.set(ThreadMXBeanUtils.getCurrentThreadTime());
    }

    public static void initCurrentThreadUserTime() {
        user_cpu_cost.set(ThreadMXBeanUtils.getCurrentThreadUserTime());
    }

    public static void clear() {
        sql_count.remove();
        sql_cost.remove();
        traceid.remove();
        acclocated_memory.remove();
        total_cpu_cost.remove();
        user_cpu_cost.remove();
    }
}

3.2、引入CPU使用统计

配置文件可以配置是否开启统计

thread:
  cpu_time_enabled: true
  allocated_memory_enabled: true
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ThreadMXBeanConfig implements InitializingBean {
    @Value("${thread.cpu_time_enabled:true}")
    private static boolean isThreadCpuTimeEnabled;
    @Value("${thread.allocated_memory_enabled:true}")
    private static boolean isThreadAllocatedMemoryEnabled;

    @Override
    public void afterPropertiesSet() throws Exception {
        if (isThreadCpuTimeEnabled) {
            ThreadMXBeanUtils.setThreadCpuTimeEnabled(true);
        }
        if (isThreadAllocatedMemoryEnabled) {
            ThreadMXBeanUtils.setThreadAllocatedMemoryEnabled(true);
        }
    }
}
import com.sun.management.ThreadMXBean;
import java.lang.management.ManagementFactory;

public class ThreadMXBeanUtils {
    private static final ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();

    public static void setThreadCpuTimeEnabled(boolean enabled) {
        threadMXBean.setThreadCpuTimeEnabled(enabled);
    }

    public static void setThreadAllocatedMemoryEnabled(boolean enabled) {
        threadMXBean.setThreadAllocatedMemoryEnabled(enabled);
    }

    public static Boolean isThreadCpuTimeEnabled() {
        return threadMXBean.isThreadCpuTimeEnabled();
    }

    public static Boolean isThreadAllocatedMemoryEnabled() {
        return threadMXBean.isThreadAllocatedMemoryEnabled();
    }

    public static long getCurrentThreadTime() {
        return threadMXBean.getCurrentThreadCpuTime() / 1_000_000L;
    }

    public static long getCurrentThreadUserTime() {
        return threadMXBean.getCurrentThreadUserTime() / 1_000_000L;
    }

    public static long getCurrentThreadAllocatedBytes() {
        return threadMXBean.getCurrentThreadAllocatedBytes();
    }
}

3.3、拼接日志信息

import com.zhangziwa.practisesvr.utils.JsonUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

public class logUtils {
    public static String genUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    public static String buildApiJsonLog(HttpServletRequest request, HttpServletResponse response, long cost) {
        Map<String, Object> apiJsonMap = new LinkedHashMap<>();
        apiJsonMap.put("traceId", LogContext.getTraceId());
        apiJsonMap.put("end_date", DateTimeFormatter.ISO_ZONED_DATE_TIME.format(ZonedDateTime.now()));
        apiJsonMap.put("cost", cost);
        apiJsonMap.put("remoteHost", request.getRemoteHost());
        apiJsonMap.put("remoteAddr", request.getRemoteAddr());
        apiJsonMap.put("remotePort", request.getRemotePort());
        apiJsonMap.put("method", request.getMethod());
        apiJsonMap.put("requestURI", request.getRequestURI());
        apiJsonMap.put("status", response.getStatus());
        apiJsonMap.put("requestContentLength", request.getContentLengthLong());
        apiJsonMap.put("sql_count", LogContext.getSqlCost());
        if (Boolean.TRUE.equals(ThreadMXBeanUtils.isThreadCpuTimeEnabled())) {
            apiJsonMap.put("currentThreadTime", ThreadMXBeanUtils.getCurrentThreadTime());
            apiJsonMap.put("currentThreadUserTime", ThreadMXBeanUtils.getCurrentThreadUserTime());
        }
        if (Boolean.TRUE.equals(ThreadMXBeanUtils.isThreadAllocatedMemoryEnabled())) {
            apiJsonMap.put("currentThreadAllocatedBytes", ThreadMXBeanUtils.getCurrentThreadAllocatedBytes());
        }
        return JsonUtils.toJson(apiJsonMap);
    }
}

第4步:使用

[2024-01-22 23:59:54.392_392] [WARN ] [http-nio-8080-exec-3] [LogFilter.java:21] → [LogFilter.doFilter: Start processing request at 2024-01-22T15:59:54.392746300Z - /students]
***LogFilter.doFilter.start***
***RequestHeaderCheckFilter.doFilter.start***

***ResponsePostInterceptor.preHandle***
***LogInterceptor.preHandle***
[2024-01-22 23:59:54.414_414] [WARN ] [http-nio-8080-exec-3] [LogInterceptor.java:36] → [LogInterceptor.postHandle: Start processing request at 2024-01-22T15:59:54.414364Z - /students]

***StudentController.edit***
[2024-01-22 23:59:56.589_589] [INFO ] [http-nio-8080-exec-3] [HikariDataSource.java:110] → [practisedb - Starting...]
[2024-01-22 23:59:56.730_730] [INFO ] [http-nio-8080-exec-3] [HikariPool.java:565] → [practisedb - Added connection com.mysql.cj.jdbc.ConnectionImpl@1e073db7]
[2024-01-22 23:59:56.732_732] [INFO ] [http-nio-8080-exec-3] [HikariDataSource.java:123] → [practisedb - Start completed.]

***ResponsePostAdvice.supports***
***ResponsePostAdvice.beforeBodyWrite***

***LogInterceptor.postHandle***
***ResponsePostInterceptor.postHandle***

***LogInterceptor.afterCompletion***
[2024-01-22 23:59:57.328_328] [WARN ] [http-nio-8080-exec-3] [LogInterceptor.java:56] → [LogInterceptor.postHandle: Finished processing request at 2024-01-22T15:59:57.328849300Z - /students in 2914 ms. Status code: 200]
[2024-01-22 23:59:57.715_715] [INFO ] [http-nio-8080-exec-3] [LogInterceptor.java:60] → [{"traceId":"5fef66027b0b45b1a509b7c1c4388b28","end_date":"2024-01-22T23:59:57.5332642+08:00[Asia/Shanghai]","cost":2914,"remoteHost":"0:0:0:0:0:0:0:1","remoteAddr":"0:0:0:0:0:0:0:1","remotePort":8263,"method":"PUT","requestURI":"/students","status":200,"requestContentLength":180,"sql_count":0,"currentThreadTime":109,"currentThreadUserTime":93,"currentThreadAllocatedBytes":29778776}]
[2024-01-22 23:59:57.715_715] [WARN ] [http-nio-8080-exec-3] [LogFilter.java:30] → [LogFilter.doFilter: Finished processing request at 2024-01-22T15:59:57.715645Z - /students in 3323 ms. Status code: 200]
***ResponsePostInterceptor.afterCompletion***

***RequestHeaderCheckFilter.doFilter.end***
***LogFilter.doFilter.end***