开发者

springboot中的日志实现方式

开发者 https://www.devze.com 2025-12-27 09:04 出处:网络 作者: kkkkkkkkl24
价值2999元 Java视频教程限时免费下载
专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
立即下载
目录一:观测1.1 “事件记录”1.2 “系统运行状态的数字化”1.3 “服务之间的全链路剖面”二:日志2.1依赖2.2配置三:异常机制3.1统一相应结构3.2错误码枚举3.3业务异常类3.4全局异常
目录
  • 一:观测
    • 1.1 “事件记录”
    • 1.2 “系统运行状态的数字化”
    • 1.3 “服务之间的全链路剖面”
  • 二:日志
    • 2.1依赖
    • 2.2配置
  • 三:异常机制
    • 3.1统一相应结构
    • 3.2错误码枚举
    • 3.3业务异常类
    • 3.4全局异常处理器(核心)
  • 四:日志进阶
    • 4.1加强日志
    • 4.2TraceId 工具类
    • 4.3Filter
    • 4.4线程池追踪
  • 总结

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

    • 哪个接口慢?
    • 哪个服务 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.RollingFileAppenderhttp://www.devze.com">
            <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, rejssponse);
            } finally {
                TraceUtil.clear();
            }
        }
    }
    

    4.4线程池追踪

    @Configuration
    public class ThreadPoolConfig {
    
        @Bean("commonExecutor")
        public ThreadPoolTaskExecutor commonExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    
          www.devze.com  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;
        }
    }
    

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0
    价值2999元 Java视频教程限时免费下载
    专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
    立即下载

    精彩评论

    暂无评论...
    验证码 换一张
    取 消