springboot日志实现

很多人写代码,只管业务逻辑,不知道系统在跑的时候:

  • 哪个接口慢?

  • 哪个服务 QPS 高?

  • 哪个下游抖了?

  • 哪条链路撑不住了?

  • 哪个线程池快爆了?

  • 哪次 GC 卡顿导致 RT 抖动?

这些其实都可以通过观测发现,所以我想通过一系列的文章分享下"观测"的实现。

一:观测

1.1 "事件记录"

你可以理解为:
"发生了什么"

比如:订单创建、用户登录、异常、重试、降级等等。

如果没有 traceId,日志是碎片;

如果有 traceId,日志就是"故事"。

1.2 "系统运行状态的数字化"

你可以理解为:
"系统现在的健康状况是什么"

比如:

  • QPS:有没有被压?

  • RT:变慢了吗?

  • P99:高峰压力如何?

  • JVM 堆:是否泄漏?

  • 线程池:是否被打满?

1.3 "服务之间的全链路剖面"

你可以理解为:
"系统调用链长什么样"

比如:一次下单 → 走了哪些服务、哪些接口、哪些耗时?

二:日志

2.1依赖

java 复制代码
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.3</version>
</dependency>

2.2配置

在resources文件夹下,添加logback-spring.xml文件,配置如下,

java 复制代码
​
<configuration>
    <appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>具体文件路径</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>具体文件路径.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>打印日志在控制台(可以删除,减小开销)
        <appender-ref ref="JSON_FILE"/>输出日志到文件
    </root>
</configuration>

​

三:异常机制

3.1统一相应结构

java 复制代码
@Data
public class ApiResponse<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> resp = new ApiResponse<>();
        resp.setCode(0);
        resp.setMessage("OK");
        resp.setData(data);
        return resp;
    }

    public static ApiResponse<?> fail(Integer code, String message) {
        ApiResponse<?> resp = new ApiResponse<>();
        resp.setCode(code);
        resp.setMessage(message);
        return resp;
    }
}

3.2错误码枚举

java 复制代码
@Getter
public enum ErrorCode {
    SYSTEM_ERROR(10001, "系统异常,请稍后再试"),
    BAD_REQUEST(10002, "请求参数错误"),
    NOT_FOUND(10003, "资源不存在"),
    BUSINESS_ERROR(20001, "业务异常");

    private final int code;
    private final String msg;

    ErrorCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

3.3业务异常类

java 复制代码
@Getter
public class BizException extends RuntimeException {

    private final int code;

    public BizException(ErrorCode errorCode) {
        super(errorCode.getMsg());
        this.code = errorCode.getCode();
    }
}

3.4全局异常处理器(核心)

java 复制代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BizException.class)
    public ApiResponse<?> handleBizException(BizException e) {
        log.warn("Business exception: {}", e.getMessage(), e);
        return ApiResponse.fail(e.getCode(), e.getMessage());
    }

    /**
     * 处理系统异常
     */
    @ExceptionHandler(Exception.class)
    public ApiResponse<?> handleException(Exception e) {
        log.error("System exception:", e);
        return ApiResponse.fail(
                ErrorCode.SYSTEM_ERROR.getCode(),
                ErrorCode.SYSTEM_ERROR.getMsg()
        );
    }
}

四:日志进阶

4.1加强日志

java 复制代码
<configuration>

    <!-- JSON 文件输出,按日期滚动 -->
    <appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>你的文件名</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>你的文件名.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>

         <!-- 核心:使用 Composite Encoder,让我们能添加 MDC -->
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <!-- 时间戳 -->
                <timestamp />

                <!-- 日志等级 -->
                <logLevel />

                <!-- 线程名 -->
                <threadName />

                <!-- 日志位置 -->
                <callerData />

                <!-- 日志内容 -->
                <message />

                <!-- 核心:输出 MDC,如 traceId -->
                <mdc />

                <!-- 记录 logger 名字 -->
                <loggerName />
            </providers>
        </encoder>
    </appender>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="JSON_FILE" />
    </root>
</configuration>

4.2TraceId 工具类

java 复制代码
//追踪id
public class TraceUtil {

    private static final String TRACE_ID = "traceId";

    public static String initTrace() {
        String traceId = UUID.randomUUID().toString().replace("-", "");
        MDC.put(TRACE_ID, traceId);
        return traceId;
    }

    public static void setTrace(String traceId) {
        MDC.put(TRACE_ID, traceId);
    }

    public static void clear() {
        MDC.remove(TRACE_ID);
    }

    public static String getTrace() {
        return MDC.get(TRACE_ID);
    }
}

4.3Filter

java 复制代码
@Component
public class TraceIdFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
                                    throws ServletException, IOException {

        // 如果 header 已经有 traceId,如网关或 NGINX 传递
        String traceId = request.getHeader("traceId");

        if (traceId == null || traceId.isEmpty()) {
            traceId = TraceUtil.initTrace();
        } else {
            TraceUtil.setTrace(traceId);
        }

        try {
            filterChain.doFilter(request, response);
        } finally {
            TraceUtil.clear();
        }
    }
}

4.4线程池追踪

java 复制代码
@Configuration
public class ThreadPoolConfig {

    @Bean("commonExecutor")
    public ThreadPoolTaskExecutor commonExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("common-exec-");

        // 拒绝策略 
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

        // 任务装饰器(增强:日志、链路追踪、异常等)
        executor.setTaskDecorator(runnable -> () -> {
            try {
                runnable.run();
            } catch (Exception e) {
                System.err.println("Execute error: " + e.getMessage());
                throw e;
            }
        });

        executor.initialize();
        return executor;
    }
}

日志篇就到此结束了,之后有空会出剩下的内容的

相关推荐
Sally_xy42 分钟前
安装 Docker
java·docker·容器
洛克大航海1 小时前
Maven 的下载安装配置教程
java·maven
雨中飘荡的记忆1 小时前
Spring MVC详解
java·spring
即将进化成人机1 小时前
Spring Boot入门
java·spring boot·后端
苏打水com1 小时前
HTML/CSS 核心考点详解(字节跳动 ToB 中台场景)
java·前端·javascript
-大头.1 小时前
Spring批处理与任务管理全解析
java·linux·spring
科普瑞传感仪器1 小时前
基于六维力传感器的机器人柔性装配,如何提升发动机零部件装配质量?
java·前端·人工智能·机器人·无人机
她说..1 小时前
Java AOP完全指南:从原理到实战(全套知识点+场景总结)
java·开发语言·spring·java-ee·springboot
-大头.1 小时前
Spring进阶:构建模块化RESTful系统全攻略
java·spring·restful