羊宝爱吃鸡丝凉面 發表於 2025-12-26 09:35:07

springboot中的日志实现方式

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一:观测</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">1.1 &ldquo;事件记录&rdquo;</a></li><li><a href="#_lab2_0_1">1.2 &ldquo;系统运行状态的数字化&rdquo;</a></li><li><a href="#_lab2_0_2">1.3 &ldquo;服务之间的全链路剖面&rdquo;</a></li></ul><li><a href="#_label1">二:日志</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_3">2.1依赖</a></li><li><a href="#_lab2_1_4">2.2配置</a></li></ul><li><a href="#_label2">三:异常机制</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_5">3.1统一相应结构</a></li><li><a href="#_lab2_2_6">3.2错误码枚举</a></li><li><a href="#_lab2_2_7">3.3业务异常类</a></li><li><a href="#_lab2_2_8">3.4全局异常处理器(核心)</a></li></ul><li><a href="#_label3">四:日志进阶</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_9">4.1加强日志</a></li><li><a href="#_lab2_3_10">4.2TraceId 工具类</a></li><li><a href="#_lab2_3_11">4.3Filter</a></li><li><a href="#_lab2_3_12">4.4线程池追踪</a></li></ul><li><a href="#_label4">总结</a></li><ul class="second_class_ul"></ul></ul></div><p>很多人写代码,只管业务逻辑,不知道系统在跑的时候:</p>
<ul><li>哪个接口慢?</li><li>哪个服务 QPS 高?</li><li>哪个下游抖了?</li><li>哪条链路撑不住了?</li><li>哪个线程池快爆了?</li><li>哪次 GC 卡顿导致 RT 抖动?</li></ul>
<p>这些其实都可以通过观测发现,所以我想通过一系列的文章分享下&ldquo;观测&rdquo;的实现。</p>
<p class="maodian"><a name="_label0"></a></p><h2>一:观测</h2>
<p class="maodian"><a name="_lab2_0_0"></a></p><h3>1.1 &ldquo;事件记录&rdquo;</h3>
<p>你可以理解为:</p>
<p><strong>&ldquo;发生了什么&rdquo;</strong></p>
<p>比如:订单创建、用户登录、异常、重试、降级等等。</p>
<ul><li>如果没有 traceId,日志是碎片;</li><li>如果有 traceId,日志就是&ldquo;故事&rdquo;。</li></ul>
<p class="maodian"><a name="_lab2_0_1"></a></p><h3>1.2 &ldquo;系统运行状态的数字化&rdquo;</h3>
<p>你可以理解为:</p>
<p><strong>&ldquo;系统现在的健康状况是什么&rdquo;</strong></p>
<p>比如:</p>
<ul><li>QPS:有没有被压?</li><li>RT:变慢了吗?</li><li>P99:高峰压力如何?</li><li>JVM 堆:是否泄漏?</li><li>线程池:是否被打满?</li></ul>
<p class="maodian"><a name="_lab2_0_2"></a></p><h3>1.3 &ldquo;服务之间的全链路剖面&rdquo;</h3>
<p>你可以理解为:</p>
<p><strong>&ldquo;系统调用链长什么样&rdquo;</strong></p>
<p>比如:一次下单 &rarr; 走了哪些服务、哪些接口、哪些耗时?</p>
<p class="maodian"><a name="_label1"></a></p><h2>二:日志</h2>
<p class="maodian"><a name="_lab2_1_3"></a></p><h3>2.1依赖</h3>
<div class="jb51code"><pre class="brush:xml;">&lt;dependency&gt;
    &lt;groupId&gt;net.logstash.logback&lt;/groupId&gt;
    &lt;artifactId&gt;logstash-logback-encoder&lt;/artifactId&gt;
    &lt;version&gt;7.3&lt;/version&gt;
&lt;/dependency&gt;
</pre></div>
<p class="maodian"><a name="_lab2_1_4"></a></p><h3>2.2配置</h3>
<p>在resources文件夹下,添加logback-spring.xml文件,配置如下,</p>
<div class="jb51code"><pre class="brush:xml;">&lt;configuration&gt;
    &lt;appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"&gt;
      &lt;file&gt;具体文件路径&lt;/file&gt;
      &lt;rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"&gt;
            &lt;fileNamePattern&gt;具体文件路径.%d{yyyy-MM-dd}.log&lt;/fileNamePattern&gt;
            &lt;maxHistory&gt;7&lt;/maxHistory&gt;
      &lt;/rollingPolicy&gt;
      &lt;encoder class="net.logstash.logback.encoder.LogstashEncoder"/&gt;
    &lt;/appender&gt;

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

    &lt;root level="INFO"&gt;
      &lt;appender-ref ref="CONSOLE"/&gt;打印日志在控制台(可以删除,减小开销)
      &lt;appender-ref ref="JSON_FILE"/&gt;输出日志到文件
    &lt;/root&gt;
&lt;/configuration&gt;</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>三:异常机制</h2>
<p class="maodian"><a name="_lab2_2_5"></a></p><h3>3.1统一相应结构</h3>
<div class="jb51code"><pre class="brush:sql;">@Data
public class ApiResponse&lt;T&gt; {
    private Integer code;
    private String message;
    private T data;

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

    public static ApiResponse&lt;?&gt; fail(Integer code, String message) {
      ApiResponse&lt;?&gt; resp = new ApiResponse&lt;&gt;();
      resp.setCode(code);
      resp.setMessage(message);
      return resp;
    }
}
</pre></div>
<p class="maodian"><a name="_lab2_2_6"></a></p><h3>3.2错误码枚举</h3>
<div class="jb51code"><pre class="brush:sql;">@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;
    }
}
</pre></div>
<p class="maodian"><a name="_lab2_2_7"></a></p><h3>3.3业务异常类</h3>
<div class="jb51code"><pre class="brush:sql;">@Getter
public class BizException extends RuntimeException {

    private final int code;

    public BizException(ErrorCode errorCode) {
      super(errorCode.getMsg());
      this.code = errorCode.getCode();
    }
}
</pre></div>
<p class="maodian"><a name="_lab2_2_8"></a></p><h3>3.4全局异常处理器(核心)</h3>
<div class="jb51code"><pre class="brush:sql;">@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

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

    /**
   * 处理系统异常
   */
    @ExceptionHandler(Exception.class)
    public ApiResponse&lt;?&gt; handleException(Exception e) {
      log.error("System exception:", e);
      return ApiResponse.fail(
                ErrorCode.SYSTEM_ERROR.getCode(),
                ErrorCode.SYSTEM_ERROR.getMsg()
      );
    }
}
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>四:日志进阶</h2>
<p class="maodian"><a name="_lab2_3_9"></a></p><h3>4.1加强日志</h3>
<div class="jb51code"><pre class="brush:xml;">&lt;configuration&gt;

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

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

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

                &lt;!-- 日志等级 --&gt;
                &lt;logLevel /&gt;

                &lt;!-- 线程名 --&gt;
                &lt;threadName /&gt;

                &lt;!-- 日志位置 --&gt;
                &lt;callerData /&gt;

                &lt;!-- 日志内容 --&gt;
                &lt;message /&gt;

                &lt;!-- 核心:输出 MDC,如 traceId --&gt;
                &lt;mdc /&gt;

                &lt;!-- 记录 logger 名字 --&gt;
                &lt;loggerName /&gt;
            &lt;/providers&gt;
      &lt;/encoder&gt;
    &lt;/appender&gt;

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

    &lt;root level="INFO"&gt;
      &lt;appender-ref ref="CONSOLE" /&gt;
      &lt;appender-ref ref="JSON_FILE" /&gt;
    &lt;/root&gt;
&lt;/configuration&gt;
</pre></div>
<p class="maodian"><a name="_lab2_3_10"></a></p><h3>4.2TraceId 工具类</h3>
<div class="jb51code"><pre class="brush:sql;">//追踪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);
    }
}
</pre></div>
<p class="maodian"><a name="_lab2_3_11"></a></p><h3>4.3Filter</h3>
<div class="jb51code"><pre class="brush:sql;">@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();
      }
    }
}
</pre></div>
<p class="maodian"><a name="_lab2_3_12"></a></p><h3>4.4线程池追踪</h3>
<div class="jb51code"><pre class="brush:sql;">@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 -&gt; () -&gt; {
            try {
                runnable.run();
            } catch (Exception e) {
                System.err.println("Execute error: " + e.getMessage());
                throw e;
            }
      });

      executor.initialize();
      return executor;
    }
}
</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>总结</h2>
<p>以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
頁: [1]
查看完整版本: springboot中的日志实现方式