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;
    }
}

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

相关推荐
毕设源码-郭学长1 天前
【开题答辩全过程】以 基于SpringBoot技术的美妆销售系统为例,包含答辩的问题和答案
java·spring boot·后端
梨落秋霜1 天前
Python入门篇【文件处理】
android·java·python
N***H4861 天前
springcloud springboot nacos版本对应
spring boot·spring·spring cloud
Java 码农1 天前
RabbitMQ集群部署方案及配置指南03
java·python·rabbitmq
哈库纳玛塔塔1 天前
放弃 MyBatis,拥抱新一代 Java 数据访问库
java·开发语言·数据库·mybatis·orm·dbvisitor
S***q3771 天前
Spring Boot管理用户数据
java·spring boot·后端
BD_Marathon1 天前
SpringBoot——辅助功能之切换web服务器
服务器·前端·spring boot
天“码”行空1 天前
java面向对象的三大特性之一多态
java·开发语言·jvm
毕设源码-郭学长1 天前
【开题答辩全过程】以 基于SpringBoot框架的民俗文化交流与交易平台的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
l***21781 天前
SpringBoot Maven快速上手
spring boot·后端·maven