---
URL: "/zh-CN/blog/2026-03/spring-boot-exception-log.html"
LLMS_URL: "/zh-CN/blog/2026-03/spring-boot-exception-log.md"
lastUpdated: true
commentabled: true
recommended: true
title: "Spring Boot 项目优雅上线"
description: "日志、监控、异常处理最佳实践"
date:

pageClass: "blog-page-class"
cover: "/covers/springboot.svg"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: Spring Boot 项目优雅上线
description: 日志、监控、异常处理最佳实践
date: 2026-03-16 09:35:00 
pageClass: blog-page-class
cover: /covers/springboot.svg
---

> 导读：代码写完了，测试也过了，但上线后才是真正考验的开始。半夜报警电话响起、用户反馈“页面白屏”、排查问题如大海捞针……这些场景你是否熟悉？
> 一个成熟的 Spring Boot 应用，不仅要有强大的业务逻辑，更必须具备可观测性（Observability）和健壮性。本文将从结构化日志、全链路监控、全局异常处理三个维度，手把手教你打造“生产级”的 Spring Boot 应用，让上线不再是噩梦，而是从容的交付。

## 一、日志篇：从“乱码堆”到“数据金矿” ##

很多开发者的日志习惯是：`System.out.println` 或者随意打 `logger.info("debug..." + obj)`。这在开发环境没问题，但在生产环境就是灾难。

### 必须使用结构化日志 (JSON Format) ###

传统文本日志难以被机器自动分析。在生产环境，JSON 格式是标准。

✅ 最佳实践：引入 `logstash-logback-encoder`，将日志输出为 JSON。

```xml
<!-- pom.xml -->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>9.0</version>
</dependency>
```

```xml
<!-- logback-spring.xml -->
<configuration>
    <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp/>
                <loggerName/>
                <threadName/>
                <level/>
                <message/>
                <!-- 关键：添加追踪ID，方便链路追踪 -->
                <mdc>
                    <excludeMdcKeyName>traceId</excludeMdcKeyName> 
                </mdc>
                <pattern>
                    <pattern>{"traceId": "%X{traceId}"}</pattern>
                </pattern>
                <stackTrace/>
            </providers>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="JSON_CONSOLE"/>
    </root>
</configuration>
```

> 收益：ELK (Elasticsearch, Logstash, Kibana) 或 Loki 可以直接解析字段，支持按 traceId、level、className 快速检索。

### 动态调整日志级别 (无需重启) ###

生产环境出现 Bug，需要临时开启 DEBUG 日志复现问题，难道要重新打包部署？NO！

✅ 最佳实践：利用 Spring Boot Actuator 动态调整。

```xml
<!-- 引入 Actuator -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```

```yaml
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: loggers # 只暴露日志控制端点，注意安全！
  endpoint:
    loggers:
      enabled: true
```

操作示例：

```bash
# 查看当前级别
curl http://localhost:8080/actuator/loggers/com.example.service

# 动态调整为 DEBUG
curl -X POST http://localhost:8080/actuator/loggers/com.example.service \
  -H 'Content-Type: application/json' \
  -d '{"configuredLevel": "DEBUG"}'
```

### 敏感数据脱敏 ###

日志中严禁出现明文密码、手机号、身份证、银行卡号。

✅ 最佳实践：自定义 Converter 或使用脱敏工具类。

```java
// 简单示例：在打印对象前处理
public class SensitiveInfoUtils {
    public static String maskPhone(String phone) {
        if (phone == null) return null;
        return phone.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2");
    }
}
// 在 toString() 或 JSON 序列化器中调用
```

## 二、监控篇：从“黑盒”到“透明玻璃房” ##

没有监控的系统就是在裸奔。你需要知道：系统活著吗？慢吗？错了吗？

### 核心指标体系 (RED & USE 方法) ###

- RED (Request, Error, Duration) ：针对微服务接口。

  - Rate (QPS)：每秒请求数。
  - Error Rate：错误率（4xx, 5xx）。
  - Duration：响应时间（P99, P95, Avg）。

- USE (Utilization, Saturation, Errors) ：针对基础设施。

- CPU、内存、磁盘 IO、网络带宽。

### 集成 Prometheus + Grafana ###

Spring Boot Actuator 原生支持 Prometheus。

✅ 配置步骤：

引入依赖：

```xml
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
```

开启端点：

```yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
  metrics:
    tags:
      application: ${spring.application.name}
```

自定义业务指标：

```java
@Component
public class CustomMetrics {
    private final Counter orderCounter;
    private final Timer orderTimer;

    public CustomMetrics(MeterRegistry registry) {
        this.orderCounter = Counter.builder("order.created.total")
                .description("Total orders created")
                .register(registry);
        
        this.orderTimer = Timer.builder("order.process.time")
                .description("Time to process an order")
                .register(registry);
    }

    public void recordOrder(long timeMs) {
        orderCounter.increment();
        orderTimer.record(timeMs, TimeUnit.MILLISECONDS);
    }
}
```

可视化：在 Grafana 中导入 Dashboard ID 6756 (JVM Micrometer) 或自定义面板。

### 健康检查与就绪探针 (K8s 必备) ###

区分 Liveness (存活) 和 Readiness (就绪)。

```yaml
management:
  health:
    livenessState:
      enabled: true
    readinessState:
      enabled: true
  endpoint:
    health:
      probes:
        enabled: true
```

- `/actuator/health/liveness`：进程死锁、OOM 时返回 503，K8s 重启容器。
- `/actuator/health/readiness`：依赖（DB, Redis）未连通时返回 503，K8s 不转发流量。

## 三、异常处理篇：从“堆栈爆炸”到“友好提示” ##

直接抛出原始异常给前端是极其不专业的，既暴露系统细节，又让用户困惑。

### 统一全局异常处理器 (@RestControllerAdvice) ###

✅ 最佳实践：定义统一的响应结构 `Result<T>` 和全局处理器。

```java
// 统一响应体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data, System.currentTimeMillis());
    }

    public static <T> Result<T> fail(Integer code, String message) {
        return new Result<>(code, message, null, System.currentTimeMillis());
    }
}

// 全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Void> handleBusinessException(BusinessException e) {
        log.warn("业务异常：{}", e.getMessage(), e); // 记录日志，但堆栈可选
        return Result.fail(400, e.getMessage());
    }

    // 处理参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException e) {
        Map<String, String> errors = new HashMap<>();
        e.getBindingResult().getFieldErrors().forEach(err -> 
            errors.put(err.getField(), err.getDefaultMessage())
        );
        log.warn("参数校验失败：{}", errors);
        return Result.fail(400, "参数校验失败", errors); // 需重载 fail 方法
    }

    // 兜底：处理未知异常
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Void> handleUnknownException(Exception e, HttpServletRequest request) {
        // 关键：记录完整堆栈和请求上下文
        String traceId = MDC.get("traceId");
        log.error("系统未知异常 [TraceId: {}] [URL: {}]", traceId, request.getRequestURI(), e);
        return Result.fail(500, "系统繁忙，请稍后重试 (Error Code: " + generateErrorCode() + ")");
    }
    
    private String generateErrorCode() {
        return UUID.randomUUID().toString().substring(0, 8);
    }
}
```

### 自定义业务异常 ###

不要直接用 RuntimeException，要语义化。

```java
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 使用
if (stock < 0) {
    throw new BusinessException("库存不足");
}
```

### 异常告警策略 ###

不是所有异常都要打电话报警。

- 忽略：参数校验错误（400），通常是用户问题。
- 警告：业务逻辑错误（如库存不足），记录日志即可。
- 严重：空指针、数据库连接超时、第三方服务不可用（500），立即触发 PagerDuty/钉钉/企业微信告警。

技巧：结合 Sentry 或自研告警系统，对 `log.error` 进行聚合，同一错误 1 分钟内只报一次，防止风暴。

## 四、进阶：链路追踪 (Distributed Tracing) ##

在微服务架构中，一个请求可能经过 10 个服务。哪里慢了？哪里错了？

✅ 最佳实践：集成 Micrometer Tracing (Spring Boot 3 默认) + Zipkin/Jaeger/SkyWalking。

引入依赖 (以 Zipkin 为例)：

```xml
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>
```

配置：

```yaml
management:
  tracing:
    sampling:
      probability: 1.0 # 生产环境建议调低，如 0.1
  zipkin:
    tracing:
      endpoint: http://zipkin-server:9411/api/v2/spans
```

效果：

每个请求生成唯一的 traceId，贯穿所有服务和日志。在 Zipkin 界面可以看到完整的调用拓扑图和耗时瀑布图。

## 五、上线检查清单 (Checklist) ##

在点击“发布”按钮前，请确认：

- 日志：是否已切换为 JSON 格式？敏感字段是否脱敏？
- 级别：生产环境默认是否为 INFO？是否保留了动态调整能力？
- 监控：Prometheus 是否能抓取到指标？Grafana 大盘是否就绪？
- 健康检查：/actuator/health 是否正常？K8s 探针是否配置正确？
- 异常：全局处理器是否捕获了所有 Exception？错误码是否规范？
- 追踪：TraceId 是否打印在日志中？链路追踪系统是否连通？
- 告警：关键指标（CPU > 80%, Error Rate > 1%）是否配置了告警规则？

## 结语 ##

优雅上线不仅仅是把代码跑起来，而是构建一个可观察、可维护、可信赖的系统。

- 日志是你的眼睛，帮你看见过去；
- 监控是你的仪表盘，帮你把握现在；
- 异常处理是你的安全气囊，帮你抵御意外；
- 链路追踪是你的导航仪，帮你理清复杂关系。

投入时间做好这四点，你将告别“救火式”运维，享受技术带来的掌控感。
