springboot中的日志实现方式

 更新时间:2025年12月26日 09:35:07   作者:kkkkkkkkl24  
文章主要介绍了如何通过观测来提升系统的性能和稳定性,包括事件记录、系统运行状态的数字化、服务之间的全链路剖面等,同时,文章还分享了日志记录、异常机制和日志进阶的相关知识

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

  • 哪个接口慢?
  • 哪个服务 QPS 高?
  • 哪个下游抖了?
  • 哪条链路撑不住了?
  • 哪个线程池快爆了?
  • 哪次 GC 卡顿导致 RT 抖动?

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

一:观测

1.1 “事件记录”

你可以理解为:

“发生了什么”

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

  • 如果没有 traceId,日志是碎片;
  • 如果有 traceId,日志就是“故事”。

1.2 “系统运行状态的数字化”

你可以理解为:

“系统现在的健康状况是什么”

比如:

  • QPS:有没有被压?
  • RT:变慢了吗?
  • P99:高峰压力如何?
  • JVM 堆:是否泄漏?
  • 线程池:是否被打满?

1.3 “服务之间的全链路剖面”

你可以理解为:

“系统调用链长什么样”

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

二:日志

2.1依赖

<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.3</version>
</dependency>

2.2配置

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

<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统一相应结构

@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错误码枚举

@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业务异常类

@Getter
public class BizException extends RuntimeException {

    private final int code;

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

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

@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加强日志

<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 工具类

//追踪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

@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线程池追踪

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

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • MySQL三种常用存储引擎InnoDB、MyISAM、Memory深度解析

    MySQL三种常用存储引擎InnoDB、MyISAM、Memory深度解析

    在线教程千万篇,为什么你的 SQL 还是慢?因为没理解存储引擎的底层机制,本文将带你从源码角度彻底搞懂 InnoDB 行锁、聚簇索引,并给出生产环境的最优选型,需要的朋友可以参考下
    2026-05-05
  • MySQL脚本批量自动插入数据及数据可按条件插入实现

    MySQL脚本批量自动插入数据及数据可按条件插入实现

    在初始化数据库或者导入一些数据时,常常会用到批量的操作,本文主要介绍了MySQL脚本批量自动插入数据及数据可按条件插入实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • MySQL中EXPLAIN语句及用法实例

    MySQL中EXPLAIN语句及用法实例

    我们常常用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,下面这篇文章主要给大家介绍了关于MySQL中EXPLAIN语句及用法的相关资料,需要的朋友可以参考下
    2022-05-05
  • mysql查询本周内每天统计量按天展示的示例代码

    mysql查询本周内每天统计量按天展示的示例代码

    本文主要介绍了mysql查询本周内每天统计量按天展示的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • 从原理到实践详解MySQL大批量数据导入的性能优化指南

    从原理到实践详解MySQL大批量数据导入的性能优化指南

    在日常运维或数据迁移场景中,MySQL大批量数据导入慢的问题经常困扰着开发者和运维人员,本文将为大家详细介绍三大核心优化方案,帮你把数据导入效率提升10倍以上
    2025-12-12
  • 详解mysql数据库增删改操作

    详解mysql数据库增删改操作

    这篇文章主要介绍了mysql数据库增删改操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • Mysql CPU占用高的问题解决方法小结

    Mysql CPU占用高的问题解决方法小结

    最近发现php网站发布信息比较慢,而且同网站目录下的asp经常登录后立即就重新登录,立即考虑到服务器资源占用问题,所以进服务器看到原来mysql占用率较高 25-60%左右,偶尔能跑到100%,所有导致上述问题的发生
    2012-06-06
  • MySQL事务与锁实例教程详解

    MySQL事务与锁实例教程详解

    事务是指满足ACID特性的的一组操作,可以通过Commit提交事务,也可以也可以通过Rollback进行回滚。会存在中间态和一致性状态,也是真正在数据库表中存在的状态
    2022-11-11
  • mysql间隙锁的具体使用

    mysql间隙锁的具体使用

    MySQL中有多种锁类型,本文主要介绍了mysql间隙锁的具体使用,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-02-02
  • MySQL校对规则(COLLATION)的具体使用

    MySQL校对规则(COLLATION)的具体使用

    本文主要介绍了MySQL校对规则(COLLATION)的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08

最新评论