文章 601
评论 5
浏览 236341
从日志到 Trace 到 Metric:搭建 Java 后端的可观测性三件套

从日志到 Trace 到 Metric:搭建 Java 后端的可观测性三件套

一、引言

"线上服务出现延迟,你需要花多久找到问题根源?"

对于现代微服务架构来说,可观测性(Observability)已经不再是可选的加分项,而是必须具备的核心能力。一个完善的可观测性体系包含三大支柱:

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    可观测性三件套                                │
│                                                                 │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐        │
│  │   Logs      │    │   Traces    │    │   Metrics   │        │
│  │   (日志)     │    │   (链路追踪)  │    │   (指标)     │        │
│  ├─────────────┤    ├─────────────┤    ├─────────────┤        │
│  │ 发生了什么   │    │ 如何发生的   │    │ 发生得频繁吗 │        │
│  └─────────────┘    └─────────────┘    └─────────────┘        │
│                                                                 │
│           三大支柱缺一不可,共同构建完整的可观测体系                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

今天,我将带你从零开始搭建一套完整的可观测性系统,涵盖日志、链路追踪和指标三个维度。


二、技术选型

2.1 架构全景图

┌──────────────────────────────────────────────────────────────────────────┐
│                                                                          │
│                          可观测性架构全景图                               │
│                                                                          │
│  ┌──────────────┐                                                       │
│  │   订单服务    │                                                       │
│  │ (Spring Boot)│                                                       │
│  └──────┬───────┘                                                       │
│         │                                                               │
│         ├── Logback (JSON 日志) ──→ Promtail ──→ Loki ──→ Grafana     │
│         │                                                               │
│         ├── OpenTelemetry Agent ──→ Jaeger ──→ Grafana                 │
│         │                                                               │
│         └── Micrometer ──→ Prometheus ──→ Grafana                      │
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │                           Grafana                                   ││
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────────┐    ││
│  │  │ 日志查询  │  │ 链路拓扑  │  │ 指标仪表盘 │  │ 统一告警管理      │    ││
│  │  └──────────┘  └──────────┘  └──────────┘  └──────────────────┘    ││
│  └─────────────────────────────────────────────────────────────────────┘│
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

2.2 组件说明

组件作用选型理由
Logback日志采集Spring Boot 默认日志框架,成熟稳定
Promtail日志收集Loki 官方推荐,轻量级,支持 Kubernetes
Loki日志存储日志聚合系统,与 Grafana 深度集成
OpenTelemetry Agent链路追踪开源标准,支持多语言,零代码侵入
Jaeger链路存储CNCF 毕业项目,功能完善,可视化优秀
Micrometer指标采集Spring Boot 默认指标库,支持多种输出
Prometheus指标存储云原生监控标准,强大的查询语言
Grafana可视化统一仪表盘,支持多数据源

三、日志系统:Logback → Promtail → Loki → Grafana

3.1 架构设计

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                      日志系统架构                                │
│                                                                 │
│  应用层          收集层          存储层          展示层           │
│                                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ Logback  │──│ Promtail │──│  Loki    │──│ Grafana  │        │
│  │ (JSON)   │  │ (tail)   │  │ (存储)   │  │ (查询)   │        │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
│                                                                 │
│  日志格式:结构化 JSON → 采集:文件 tail → 存储:索引存储 → 查询:LQL │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.2 Logback 配置

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATH" value="./logs"/>
    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="net.logstash.logback.layout.LogstashJsonLayout">
                <includeContextName>false</includeContextName>
                <customFields>{"app_name":"order-service","environment":"dev"}</customFields>
            </layout>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/order-service.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/order-service.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="net.logstash.logback.layout.LogstashJsonLayout">
                <includeContextName>false</includeContextName>
                <customFields>{"app_name":"order-service","environment":"dev"}</customFields>
            </layout>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

日志输出示例

{
  "@timestamp": "2026-07-26T10:30:00.123+08:00",
  "@version": "1",
  "app_name": "order-service",
  "environment": "dev",
  "message": "Order created successfully",
  "logger_name": "com.example.order.service.OrderService",
  "thread_name": "http-nio-8080-exec-1",
  "level": "INFO",
  "level_value": 20000,
  "traceId": "abc123",
  "spanId": "def456",
  "orderId": "ORD-20260726-0001",
  "userId": "user-12345"
}

3.3 Promtail 配置

promtail-config.yaml

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: order-service
    static_configs:
      - targets:
          - localhost
        labels:
          job: order-service
          __path__: /logs/order-service*.log
    pipeline_stages:
      - json:
          expressions:
            timestamp: "@timestamp"
            level: level
            message: message
            logger: logger_name
            traceId: traceId
            spanId: spanId
            orderId: orderId
            userId: userId
            appName: app_name
      - labels:
          level:
          appName:
          orderId:
          userId:
      - timestamp:
          source: timestamp
          format: RFC3339

3.4 Loki 查询示例

# 查询最近 1 小时的错误日志
{job="order-service", level="ERROR"} |= "order" | tail 100

# 查询指定 traceId 的所有日志
{job="order-service"} | json | traceId="abc123"

# 查询订单创建失败的日志
{job="order-service"} |= "Order creation failed"

# 统计各级别日志数量
sum(count_over_time({job="order-service"} | json | level="ERROR" [1h]))

四、链路追踪:OpenTelemetry Agent → Jaeger

4.1 架构设计

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    链路追踪系统架构                              │
│                                                                 │
│  应用层                  采集层                  展示层          │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Order Service                                          │    │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │    │
│  │  │  CreateOrder │──│  Inventory   │──│  Payment     │   │    │
│  │  │  Controller  │  │  Service     │  │  Client      │   │    │
│  │  └──────────────┘  └──────────────┘  └──────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                          │                                       │
│                          └── OpenTelemetry Agent                 │
│                               │                                   │
│                          ┌────┴────┐                              │
│                          │ Jaeger  │                              │
│                          │ (存储)  │                              │
│                          └────┬────┘                              │
│                               │                                   │
│                          ┌────┴────┐                              │
│                          │ Grafana │                              │
│                          │ (可视化)│                              │
│                          └─────────┘                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.2 OpenTelemetry 配置

otel-config.yaml

receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:

exporters:
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:9464"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

4.3 启动应用(带 OpenTelemetry Agent)

java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.traces.exporter=jaeger \
  -Dotel.exporter.jaeger.endpoint=http://jaeger:14250 \
  -Dotel.metrics.exporter=prometheus \
  -Dotel.logs.exporter=none \
  -jar order-service.jar

4.4 手动添加 Span(可选)

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;

@Service
public class OrderService {
    
    private final Tracer tracer = GlobalOpenTelemetry.getTracer("order-service");
    
    public Order createOrder(OrderRequest request) {
        Span span = tracer.spanBuilder("createOrder").startSpan();
        try (Scope scope = span.makeCurrent()) {
            span.setAttribute("order.request.amount", request.getAmount());
            span.setAttribute("order.request.userId", request.getUserId());
            
            inventoryService.reserveStock(request.getItems());
            
            Span paymentSpan = tracer.spanBuilder("processPayment").startSpan();
            try (Scope paymentScope = paymentSpan.makeCurrent()) {
                paymentService.process(request);
            } finally {
                paymentSpan.end();
            }
            
            return orderRepository.save(order);
        } finally {
            span.end();
        }
    }
}

4.5 Jaeger 查看链路

# 访问 Jaeger UI
# http://localhost:16686

# 搜索条件
Service: order-service
Operation: createOrder
Time: Last 15 minutes

五、指标系统:Micrometer → Prometheus → Grafana

5.1 架构设计

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    指标系统架构                                  │
│                                                                 │
│  应用层              存储层              展示层                   │
│                                                                 │
│  ┌──────────┐      ┌──────────┐      ┌──────────┐               │
│  │Micrometer│──────│Prometheus│──────│ Grafana  │               │
│  │(采集)    │      │(存储)    │      │(仪表盘)  │               │
│  └──────────┘      └──────────┘      └──────────┘               │
│                                                                 │
│  指标类型:Counter / Gauge / Timer / Histogram / Summary        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.2 Micrometer 配置

pom.xml 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

application.yml 配置

management:
  endpoints:
    web:
      exposure:
        include: prometheus,health,metrics
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: order-service

5.3 自定义指标

import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Metrics;
import org.springframework.stereotype.Service;

@Service
public class OrderMetricsService {
    
    private final Counter orderCreatedCounter = Counter.builder("order.created.total")
            .description("Total number of orders created")
            .tag("status", "success")
            .register(Metrics.globalRegistry);
    
    private final Counter orderFailedCounter = Counter.builder("order.created.total")
            .description("Total number of orders created")
            .tag("status", "failed")
            .register(Metrics.globalRegistry);
    
    public void recordOrderCreated(boolean success) {
        if (success) {
            orderCreatedCounter.increment();
        } else {
            orderFailedCounter.increment();
        }
    }
    
    @Timed(value = "order.processing.time", description = "Order processing time")
    public void processOrder() {
        // 业务逻辑
    }
}

5.4 Prometheus 配置

prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'order-service'
    static_configs:
      - targets: ['app:8080']
    metrics_path: '/actuator/prometheus'
    scrape_interval: 5s

5.5 PromQL 查询示例

# 查询订单创建总数
sum(order_created_total)

# 查询订单创建成功率
sum(order_created_total{status="success"}) / sum(order_created_total)

# 查询 P99 请求延迟
histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m]))

# 查询活跃连接数
jvm_threads_live_threads

# 查询内存使用率
1 - (jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"})

# 查询请求 QPS
rate(http_server_requests_seconds_count[1m])

六、实战:订单服务完整可观测案例

6.1 服务架构

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                      订单服务架构                                │
│                                                                 │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │   API网关    │───▶│ 订单服务     │───▶│ 库存服务     │         │
│  │  Gateway    │    │ OrderService│    │ Inventory   │         │
│  └─────────────┘    └──────┬──────┘    └─────────────┘         │
│                            │                                     │
│                            ▼                                     │
│                      ┌─────────────┐                            │
│                      │ 支付服务     │                            │
│                      │ Payment     │                            │
│                      └─────────────┘                            │
│                                                                 │
│  请求链路:用户下单 → 创建订单 → 扣减库存 → 发起支付 → 支付回调     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6.2 核心代码

OrderController

@RestController
@RequestMapping("/api/orders")
@Slf4j
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private OrderMetricsService metricsService;
    
    @PostMapping
    public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
        log.info("Creating order for user: {}", request.getUserId());
        
        try {
            Order order = orderService.createOrder(request);
            metricsService.recordOrderCreated(true);
            log.info("Order created successfully: {}", order.getOrderId());
            
            return ResponseEntity.ok(OrderResponse.from(order));
        } catch (Exception e) {
            metricsService.recordOrderCreated(false);
            log.error("Order creation failed for user: {}", request.getUserId(), e);
            
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(OrderResponse.failed(e.getMessage()));
        }
    }
    
    @GetMapping("/{orderId}")
    public ResponseEntity<OrderResponse> getOrder(@PathVariable String orderId) {
        log.debug("Querying order: {}", orderId);
        
        Order order = orderService.getOrder(orderId);
        if (order == null) {
            return ResponseEntity.notFound().build();
        }
        
        return ResponseEntity.ok(OrderResponse.from(order));
    }
    
    @PostMapping("/{orderId}/payment-callback")
    public ResponseEntity<Void> paymentCallback(
            @PathVariable String orderId,
            @RequestBody PaymentCallbackRequest request) {
        
        log.info("Received payment callback for order: {}, status: {}", 
                orderId, request.getStatus());
        
        orderService.handlePaymentCallback(orderId, request);
        
        return ResponseEntity.ok().build();
    }
}

OrderService

@Service
public class OrderService {
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private OrderRepository orderRepository;
    
    public Order createOrder(OrderRequest request) {
        Order order = Order.builder()
                .orderId(generateOrderId())
                .userId(request.getUserId())
                .items(request.getItems())
                .amount(request.getAmount())
                .status(OrderStatus.PENDING)
                .build();
        
        inventoryService.reserveStock(request.getItems());
        
        PaymentResult paymentResult = paymentService.initiatePayment(order);
        order.setPaymentId(paymentResult.getPaymentId());
        
        return orderRepository.save(order);
    }
    
    public void handlePaymentCallback(String orderId, PaymentCallbackRequest request) {
        Order order = orderRepository.findByOrderId(orderId);
        if (order == null) {
            throw new OrderNotFoundException(orderId);
        }
        
        if ("SUCCESS".equals(request.getStatus())) {
            order.setStatus(OrderStatus.PAID);
            inventoryService.confirmStock(order.getItems());
        } else {
            order.setStatus(OrderStatus.PAYMENT_FAILED);
            inventoryService.releaseStock(order.getItems());
        }
        
        orderRepository.save(order);
    }
}

七、Docker Compose 一键启动

7.1 docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - OTEL_SERVICE_NAME=order-service
      - OTEL_TRACES_EXPORTER=jaeger
      - OTEL_EXPORTER_JAEGER_ENDPOINT=http://jaeger:14250
      - OTEL_METRICS_EXPORTER=prometheus
      - OTEL_LOGS_EXPORTER=none
    volumes:
      - ./logs:/logs
    depends_on:
      - jaeger
      - prometheus

  promtail:
    image: grafana/promtail:latest
    volumes:
      - ./logs:/logs
      - ./config/promtail-config.yaml:/etc/promtail/config.yaml
    depends_on:
      - loki
    command: -config.file=/etc/promtail/config.yaml

  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml

  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"
      - "14250:14250"
    environment:
      - COLLECTOR_OTLP_ENABLED=true

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - ./config/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources
      - ./config/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards
      - ./config/grafana/dashboards:/var/lib/grafana/dashboards
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - loki
      - jaeger
      - prometheus

7.2 Grafana 数据源配置

datasources.yml

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    url: http://prometheus:9090
    isDefault: false
    access: proxy
    editable: true

  - name: Loki
    type: loki
    url: http://loki:3100
    isDefault: false
    access: proxy
    editable: true

  - name: Jaeger
    type: jaeger
    url: http://jaeger:16686
    isDefault: false
    access: proxy
    editable: true

7.3 一键启动

# 启动所有服务
docker-compose up -d

# 查看服务状态
docker-compose ps

# 访问各服务
# Grafana: http://localhost:3000 (admin/admin)
# Jaeger: http://localhost:16686
# Prometheus: http://localhost:9090
# Loki: http://localhost:3100

# 停止服务
docker-compose down

八、Grafana 仪表盘实战

8.1 仪表盘设计

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    订单服务仪表盘                                │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  概览面板                                                  │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  │   │
│  │  │ QPS      │  │ 成功率   │  │ P99延迟   │  │ 活跃线程 │  │   │
│  │  └──────────┘  └──────────┘  └──────────┘  └──────────┘  │   │
│  └──────────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  请求延迟分布                                               │   │
│  │  ████████████████████░░░░░░░░░░░░░░░                      │   │
│  │  0-100ms  100-500ms  500ms-1s  1s+                        │   │
│  └──────────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  错误率趋势                                                 │   │
│  │  ████████████                                              │   │
│  │  █████████████████                                         │   │
│  │  ██████████████                                            │   │
│  │  ████████████████████                                      │   │
│  └──────────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  JVM 监控                                                  │   │
│  │  ┌──────────────┐  ┌──────────────┐                       │   │
│  │  │ 堆内存使用    │  │ GC 次数      │                       │   │
│  │  └──────────────┘  └──────────────┘                       │   │
│  └──────────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  链路追踪入口                                               │   │
│  │  [查询 Jaeger 链路]                                        │   │
│  └──────────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  实时日志流                                                 │   │
│  │  {job="order-service"} |= "ERROR" | tail 50               │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

8.2 关键指标面板

面板 1:QPS

  • 指标:rate(http_server_requests_seconds_count[1m])
  • 单位:req/s
  • 图表类型:Time series

面板 2:成功率

  • 指标:sum(http_server_requests_seconds_count{status=~"2.."}) / sum(http_server_requests_seconds_count)
  • 单位:%
  • 图表类型:Stat

面板 3:P99 延迟

  • 指标:histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m]))
  • 单位:s
  • 图表类型:Time series

面板 4:堆内存使用

  • 指标:jvm_memory_used_bytes{area="heap"} / 1024 / 1024
  • 单位:MB
  • 图表类型:Gauge

面板 5:GC 次数

  • 指标:sum(increase(jvm_gc_collection_seconds_count[1m]))
  • 单位:次/分钟
  • 图表类型:Time series

九、端到端验证流程

9.1 验证步骤

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    端到端验证流程                                │
│                                                                 │
│  Step 1: 启动环境                                                │
│  └── docker-compose up -d                                       │
│                                                                 │
│  Step 2: 发送测试请求                                             │
│  └── curl -X POST http://localhost:8080/api/orders \            │
│        -H "Content-Type: application/json" \                     │
│        -d '{"userId":"user-1","amount":100.00}'                  │
│                                                                 │
│  Step 3: 验证日志                                                │
│  └── Grafana → Explore → Loki → 查询订单创建日志                   │
│                                                                 │
│  Step 4: 验证链路                                                │
│  └── Jaeger → 搜索 order-service → 查看创建订单链路                │
│                                                                 │
│  Step 5: 验证指标                                                │
│  └── Grafana → 仪表盘 → 确认 QPS、延迟、成功率更新                  │
│                                                                 │
│  Step 6: 压测验证                                                │
│  └── wrk -t10 -c100 -d30s http://localhost:8080/api/orders       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

9.2 测试脚本

#!/bin/bash

echo "=== 端到端验证 ==="

echo ""
echo "1. 创建订单..."
ORDER_RESP=$(curl -s -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{"userId":"user-123","amount":99.99,"items":[{"productId":"prod-001","quantity":1}]}')

echo "响应: $ORDER_RESP"
ORDER_ID=$(echo $ORDER_RESP | jq -r '.orderId')

echo ""
echo "2. 查询订单..."
curl -s http://localhost:8080/api/orders/$ORDER_ID

echo ""
echo "3. 模拟支付回调..."
curl -s -X POST http://localhost:8080/api/orders/$ORDER_ID/payment-callback \
  -H "Content-Type: application/json" \
  -d '{"status":"SUCCESS","transactionId":"txn-001"}'

echo ""
echo ""
echo "=== 验证完成 ==="
echo "请访问以下地址查看:"
echo "  Grafana: http://localhost:3000"
echo "  Jaeger: http://localhost:16686"
echo "  Prometheus: http://localhost:9090"
echo "  Loki: http://localhost:3100"

十、告警配置

10.1 Prometheus 告警规则

alerting-rules.yml

groups:
  - name: order-service-alerts
    rules:
      - alert: HighErrorRate
        expr: sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) / 
              sum(rate(http_server_requests_seconds_count[5m])) > 0.1
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value }}% for order-service"

      - alert: HighLatency
        expr: histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) > 2
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "High P99 latency"
          description: "P99 latency is {{ $value }}s for order-service"

      - alert: LowMemory
        expr: 1 - (jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}) < 0.2
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Low heap memory"
          description: "Heap memory usage is {{ $value }}% full"

      - alert: HighGC
        expr: sum(increase(jvm_gc_collection_seconds_count[5m])) > 30
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "High GC frequency"
          description: "{{ $value }} GC events in last 5 minutes"

10.2 Grafana 告警通知

# 在 Grafana 中配置通知渠道
# 支持:Email、Slack、Webhook、钉钉、企业微信等

# 告警策略
# 1. 错误率 > 10% → 立即通知
# 2. P99 延迟 > 2s → 持续 1 分钟通知
# 3. 内存使用率 > 80% → 持续 5 分钟通知
# 4. GC 频繁 → 持续 1 分钟通知

十一、总结

11.1 可观测性体系核心要点

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    可观测性体系核心要点                          │
│                                                                 │
│  日志系统                                                        │
│  ├── 使用结构化 JSON 格式                                        │
│  ├── 包含 traceId/spanId 便于关联                               │
│  ├── 使用 Promtail 采集到 Loki                                   │
│  └── Grafana 统一查询                                            │
│                                                                 │
│  链路追踪                                                        │
│  ├── OpenTelemetry Agent 零代码侵入                             │
│  ├── Jaeger 存储和展示                                           │
│  ├── 支持手动添加自定义 Span                                      │
│  └── Grafana 集成链路查询                                        │
│                                                                 │
│  指标系统                                                        │
│  ├── Micrometer 标准化采集                                       │
│  ├── Prometheus 存储和查询                                       │
│  ├── 自定义业务指标                                              │
│  └── Grafana 仪表盘和告警                                         │
│                                                                 │
│  三者关联                                                        │
│  ├── 日志 → traceId → 链路详情                                   │
│  ├── 链路 → 服务 → 指标仪表盘                                     │
│  ├── 指标告警 → 日志/链路 快速定位                                │
│  └── 形成完整的可观测闭环                                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

11.2 关键配置清单

✅ 日志配置
  - Logback JSON 格式输出
  - 自定义字段(app_name, traceId, spanId)
  - 文件滚动策略
  
✅ 链路追踪配置
  - OpenTelemetry Java Agent
  - service.name 设置
  - Jaeger 端点配置
  
✅ 指标配置
  - Spring Boot Actuator + Micrometer
  - Prometheus 导出
  - 自定义业务指标
  
✅ 基础设施配置
  - docker-compose 一键启动
  - Grafana 数据源自动配置
  - 告警规则配置

11.3 下一步建议

  1. 接入更多服务:将微服务架构中的其他服务也接入可观测性体系
  2. 配置告警通知:设置钉钉/企业微信/Slack 等告警渠道
  3. 优化仪表盘:根据业务需求定制更丰富的监控面板
  4. 学习 LQL/PromQL:掌握日志和指标的高级查询技巧
  5. 探索 Tempo:尝试 Grafana Tempo 替代 Jaeger(更轻量)

💡 互动话题:你在生产环境中是如何搭建可观测性体系的?遇到过哪些挑战?欢迎在评论区分享你的经验!


标题:从日志到 Trace 到 Metric:搭建 Java 后端的可观测性三件套
作者:jiangyi
地址:http://jiangyi.space/articles/2026/07/26/1785060031494.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消