Micrometer 系列【61】统一观测:基于 Spring Boot 的生产级演示案例 | 指标集成

文章目录

  • [1. 演示说明](#1. 演示说明)
  • [2. 环境搭建](#2. 环境搭建)
    • [2.1 搭建 Spring Boot 工程](#2.1 搭建 Spring Boot 工程)
      • [2.2.1 引入依赖](#2.2.1 引入依赖)
      • [2.1.2 应用配置](#2.1.2 应用配置)
    • [2.2 Docker Compose 部署 Prometheus + Grafana](#2.2 Docker Compose 部署 Prometheus + Grafana)
  • [3. 代码实现](#3. 代码实现)
    • [3.1 上下文(Context)](#3.1 上下文(Context))
    • [3.2 观测约定(Convention)](#3.2 观测约定(Convention))
    • [3.3 观测文档化(Documentation)](#3.3 观测文档化(Documentation))
    • [3.4 指标处理器(Handler)](#3.4 指标处理器(Handler))
    • [3.5 装配(Config)](#3.5 装配(Config))
    • [3.6 业务服务(Service)](#3.6 业务服务(Service))
    • [3.7 控制器(Controller)](#3.7 控制器(Controller))
  • [4. 运行与验证](#4. 运行与验证)
    • [4.1 启动应用](#4.1 启动应用)
    • [4.2 触发业务](#4.2 触发业务)
    • [4.3 查看指标](#4.3 查看指标)
    • [4.4 Grafana 展示](#4.4 Grafana 展示)
  • [5. 总结](#5. 总结)

1. 演示说明

Micrometer Observation 把「采集一次业务操作的时间、标签、结果」这件事抽象成一个统一模型,业务代码只需要声明"这里是一次可观测操作"并填充业务上下文,剩下的指标生成、标签组装、异常记录全部交给框架完成。

本案例参考 Spring AIChatModelObservationDocumentationChatModelObservationContextChatModelObservationConvention 等类,把「订单 → 支付」完整链路做成三个互相嵌套的观测点。

三个观测点:

观测名称 指标名 含义
order.create order_create_seconds 创建订单一次操作的耗时/次数
payment.create payment_create_seconds 支付一次操作的耗时/次数
checkout.create checkout_create_seconds 结算编排(下单+支付整体),作为父观测

三者关系如下:

text 复制代码
checkout.create(父观测,openScope)
  ├── order.create    (子观测)
  └── payment.create  (子观测)

演示能力:

  1. 自动指标 :框架根据低基数标签自动生成 Timer,如 order_create_seconds_countorder_create_seconds_max
  2. 自定义业务指标 :通过 ObservationHandler 在观测停止(onStop)时写入 business_order_amountbusiness_payment_amount 等金额分布(summary)。
  3. 异常观测 :支付传入 FAIL 渠道会抛异常,observe() 自动调用 observation.error(ex),指标上会多出 error="IllegalStateException" 标签,便于统计失败率。
  4. 可观测后端对接 :指标经 /actuator/prometheus 暴露,由 Prometheus 采集、Grafana 展示。

2. 环境搭建

2.1 搭建 Spring Boot 工程

本模块是独立 Maven 工程,父 POM 直接用 spring-boot-starter-parent,版本 4.1.0Boot 4),JDK 17

2.2.1 引入依赖

要点说明:

  • micrometer-observationactuator 的传递依赖,无需单独声明;ObservationRegistryObservationHandler 等类型都来自它。
  • micrometer-registry-prometheus 负责把指标输出成 Prometheus 文本格式,由 /actuator/prometheus 提供。
xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.0</version>
</parent>

<properties>
    <java.version>17</java.version>
</properties>

<dependencies>
    <!-- 观测 API、ObservationRegistry、Actuator 端点都来自这里 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <!-- MVC 入口(Boot 4 模块化后的 starter) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>

    <!-- Prometheus 指标暴露格式(micrometer-registry-prometheus) -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>

2.1.2 应用配置

src/main/resources/application.yaml 核心片段:

yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: spring-micrometer-service   # 指标里的应用名

management:
  endpoints:
    web:
      exposure:
        include: metrics,prometheus   # 暴露指标端点
  metrics:
    enable:
      jvm: false                      # 关闭 JVM 指标,聚焦业务指标
      http.server.requests: true
      all: true

只需暴露 metricsprometheus 两个端点即可,/actuator/prometheus 就是 Prometheus 的抓取地址。

2.2 Docker Compose 部署 Prometheus + Grafana

应用运行在本机 8080 端口。通过 Docker Compose 一键拉起 Prometheus(采集)Grafana(展示) ,容器内通过 host.docker.internal 访问宿主机上的应用。

docker-compose.yaml

yaml 复制代码
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: ob-prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.path=/prometheus

  grafana:
    image: grafana/grafana:latest
    container_name: ob-grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - prometheus

prometheus.yml

yaml 复制代码
scrape_configs:
  - job_name: spring-micrometer-service
    metrics_path: /actuator/prometheus
    scrape_interval: 5s
    static_configs:
      - targets: ["host.docker.internal:8080"]

启动:

bash 复制代码
docker compose up -d
  • Prometheushttp://localhost:9090
  • Grafanahttp://localhost:3000admin/admin,数据源填 http://prometheus:9090

3. 代码实现

包结构总览(参考 Spring AI 的分层方式):

text 复制代码
ob/
├── metadata/       # 属性名常量、操作类型、操作元数据
├── context/        # 观测上下文(承载业务数据与结果)
├── documentation/  # 观测文档化枚举(定义低/高基数 KeyName)
├── convention/     # 观测约定接口 + 默认实现(决定指标名、标签)
├── handler/        # 指标处理器(自定义业务指标)
└── config/         # 把 handler 注册成 Bean

service/            # 业务服务:发起观测
controller/         # REST 入口

调用链(一次完整的观测生命周期):

text 复制代码
业务代码
  └─ Documentation.XXX.observation(custom, default, contextSupplier, registry)
        └─ .observe(() -> 业务逻辑)
              ├─ 打开 scope
              ├─ 触发 getLowCardinalityKeyValues() / getHighCardinalityKeyValues()
              ├─ 正常结束 → onStop(Timer 计数、自定义指标落库)
              └─ 异常结束 → onError → onStop(Timer 带 error 标签)

3.1 上下文(Context)

作用 :一个可携带业务数据的容器,是观测的状态对象 。它继承 Observation.Context,用 Builder 构建;输入参数(订单号、用户 ID)一旦创建不可变,业务结果(订单 ID、金额、状态)在观测执行过程中写入。

定义属性名常量与操作元数据:

java 复制代码
// BusinessObservationAttributes:集中管理所有 key 名字符串
public final class BusinessObservationAttributes {
    public static final String BUSINESS_OPERATION_TYPE = "business.operation.type";
    public static final String BUSINESS_PROVIDER       = "business.provider";
    public static final String ORDER_TYPE              = "order.type";
    public static final String ORDER_NO                = "order.no";
    public static final String USER_ID                 = "user.id";
    public static final String ORDER_ID                = "order.id";
    public static final String ORDER_AMOUNT            = "order.amount";
    public static final String ORDER_STATUS            = "order.status";
    public static final String PAY_CHANNEL             = "pay.channel";
    public static final String PAY_NO                  = "pay.no";
    public static final String PAY_AMOUNT              = "pay.amount";
    public static final String PAY_STATUS              = "pay.status";
    public static final String TOTAL_AMOUNT            = "checkout.total.amount";
    private BusinessObservationAttributes() {}
}

// BusinessOperationType:操作类型枚举,防止魔法字符串
public enum BusinessOperationType {
    ORDER("order"), PAYMENT("payment"), CHECKOUT("checkout");
    private final String value;
    public String value() { return this.value; }
    // 构造器略
}

// BusinessOperationMetadata:操作元数据 record,带非空校验
public record BusinessOperationMetadata(String operationType, String provider) {
    public BusinessOperationMetadata {
        Assert.hasText(operationType, "operationType cannot be null or empty");
        Assert.hasText(provider, "provider cannot be null or empty");
    }
    // builder() 略
}

定义观测上下文,以订单为例(支付、结算结构一致,此处省略 getter/setter):

java 复制代码
public class OrderObservationContext extends Observation.Context {

    // ------ 输入参数(final,创建后不可变)------
    private final BusinessOperationMetadata operationMetadata;
    private final String orderNo;
    private final Long userId;
    private final String orderType;

    // ------ 业务结果(观测执行过程中写入)------
    private String orderId;
    private BigDecimal amount;
    private String status;

    private OrderObservationContext(/* ... */) { /* ... */ }

    public static Builder builder() { return new Builder(); }

    // getter/setter 略,关键 Builder:
    public static final class Builder {
        private BusinessOperationMetadata operationMetadata;
        private String orderNo;
        private Long userId;
        private String orderType;

        public Builder operationMetadata(BusinessOperationMetadata v) { this.operationMetadata = v; return this; }
        public Builder orderNo(String v) { this.orderNo = v; return this; }
        public Builder userId(Long v) { this.userId = v; return this; }
        public Builder orderType(String v) { this.orderType = v; return this; }

        public OrderObservationContext build() {
            return new OrderObservationContext(operationMetadata, orderNo, userId, orderType);
        }
    }
}

三个 Context 的设计一致:OrderObservationContext(订单号/用户ID/订单类型)、PaymentObservationContext(订单ID/支付渠道)、CheckoutObservationContext(订单号/用户ID/订单类型)。区别只在于"输入参数"和"业务结果"字段不同。

3.2 观测约定(Convention)

作用 :决定这个观测叫什么名字、取哪些低基数标签、取哪些高基数属性

约定接口 (空接口,标记用途,用于 ObjectProvider 注入自定义实现):

java 复制代码
public interface OrderObservationConvention extends ObservationConvention<OrderObservationContext> {
}

默认实现指标名、上下文名称、标签的组装逻辑都在这里:

java 复制代码
public class DefaultOrderObservationConvention implements OrderObservationConvention {

    public static final String DEFAULT_NAME = "order.create";

    @Override
    public String getName() {
        return DEFAULT_NAME;
    }

    @Override
    public String getContextualName(OrderObservationContext context) {
        return "order " + context.getOrderType();   // 上下文名称,供日志/trace 阅读
    }

    @Override
    public KeyValues getLowCardinalityKeyValues(OrderObservationContext context) {
        return KeyValues.of(
            KeyValue.of("business.operation.type", context.getOperationMetadata().operationType()),
            KeyValue.of("business.provider",       context.getOperationMetadata().provider()),
            KeyValue.of("order.type",              context.getOrderType()));
    }

    @Override
    public KeyValues getHighCardinalityKeyValues(OrderObservationContext context) {
        KeyValues kv = KeyValues.of(
            KeyValue.of("order.no",  context.getOrderNo()),
            KeyValue.of("user.id",   String.valueOf(context.getUserId())));
        if (context.getOrderId() != null) {
            kv = kv.and(KeyValue.of("order.id", context.getOrderId()));
        }
        if (context.getAmount() != null) {
            kv = kv.and(KeyValue.of("order.amount", context.getAmount().toPlainString()));
        }
        if (context.getStatus() != null) {
            kv = kv.and(KeyValue.of("order.status", context.getStatus()));
        }
        return kv;
    }

    @Override
    public boolean supportsContext(Observation.Context context) {
        return context instanceof OrderObservationContext;
    }
}

注意两个细节:

  1. supportsContext 必须显式 public,否则会被编译器判为"缩小了接口方法访问权限"。
  2. 高基数标签在业务执行完才有值(订单 ID、金额),因此用 if (context.getXxx() != null) 渐进式追加。
  3. 一个 Key 同时出现在低/高基数里是允许的:低基数用于指标标签,高基数用于 span 属性,各司其职。

3.3 观测文档化(Documentation)

作用 :以枚举形式把"观测点 "整体声明出来,包括它的名字(getDefaultConvention)、它有哪些低基数 Key、哪些高基数 Key。这样一处声明,代码各处引用,避免魔法字符串散落。

java 复制代码
public enum OrderObservationDocumentation implements ObservationDocumentation {

    ORDER_CREATE {                        // 一个枚举常量 = 一个观测点
        @Override
        public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
            return DefaultOrderObservationConvention.class;
        }
        @Override
        public KeyName[] getLowCardinalityKeyNames()  { return LowCardinalityKeyNames.values(); }
        @Override
        public KeyName[] getHighCardinalityKeyNames() { return HighCardinalityKeyNames.values(); }
    };

    // 低基数 Key(作为指标标签)
    public enum LowCardinalityKeyNames implements KeyName {
        BUSINESS_OPERATION_TYPE { public String asString() { return "business.operation.type"; } },
        BUSINESS_PROVIDER       { public String asString() { return "business.provider";       } },
        ORDER_TYPE              { public String asString() { return "order.type";              } }
    }

    // 高基数 Key(作为 span 属性)
    public enum HighCardinalityKeyNames implements KeyName {
        ORDER_NO     { public String asString() { return "order.no";     } },
        USER_ID      { public String asString() { return "user.id";      } },
        ORDER_ID     { public String asString() { return "order.id";     } },
        ORDER_AMOUNT { public String asString() { return "order.amount"; } },
        ORDER_STATUS { public String asString() { return "order.status"; } }
    }
}

这里把 Key 名写在 asString() 里是为了展示结构;工程中它们指向 BusinessObservationAttributes 常量,保证"常量唯一来源"。

3.4 指标处理器(Handler)

作用 :观测生命周期钩子。框架默认的 DefaultMeterObservationHandler 已自动生成 Timer 指标,这里再自定义一个 handler,在观测 停止 时把业务金额写入 DistributionSummary,得到 business_order_amount_* 这类业务指标。

java 复制代码
public class OrderMeterObservationHandler implements ObservationHandler<OrderObservationContext> {

    private final MeterRegistry meterRegistry;

    public OrderMeterObservationHandler(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @Override
    public void onStop(OrderObservationContext context) {
        if (context.getAmount() != null) {
            this.meterRegistry.summary("business.order.amount", "order.type", context.getOrderType())
                .record(context.getAmount().doubleValue());
        }
    }

    @Override
    public boolean supportsContext(Observation.Context context) {
        return context instanceof OrderObservationContext;
    }
}

支付侧 PaymentMeterObservationHandler 结构完全一致,指标名换为 business.payment.amount、标签换为 pay.channel

3.5 装配(Config)

作用 :把自定义 handler 声明成 Spring BeanBoot 4ObservationAutoConfiguration 会自动收集容器里的 ObservationHandler Bean,挂到全局 ObservationRegistry 上,无需手动注册。

java 复制代码
@Configuration
public class BusinessObservationConfiguration {

    @Bean
    @ConditionalOnMissingBean
    OrderMeterObservationHandler orderMeterObservationHandler(MeterRegistry meterRegistry) {
        return new OrderMeterObservationHandler(meterRegistry);
    }

    @Bean
    @ConditionalOnMissingBean
    PaymentMeterObservationHandler paymentMeterObservationHandler(MeterRegistry meterRegistry) {
        return new PaymentMeterObservationHandler(meterRegistry);
    }
}

3.6 业务服务(Service)

作用 :真正的观测发起点。业务代码只做三件事:建 Context → 取 Observation → 包住业务逻辑

订单服务(OrderService):

java 复制代码
@Service
public class OrderService {

    private final ObservationRegistry observationRegistry;
    private final ObjectProvider<OrderObservationConvention> observationConvention; // 允许用户覆盖约定

    // 构造器注入略

    public OrderResult createOrder(String orderNo, Long userId, String orderType) {
        OrderObservationContext context = OrderObservationContext.builder()
            .operationMetadata(BusinessOperationMetadata.builder()
                .operationType(BusinessOperationType.ORDER.value())
                .provider("order-service")
                .build())
            .orderNo(orderNo)
            .userId(userId)
            .orderType(orderType)
            .build();

        return OrderObservationDocumentation.ORDER_CREATE
            .observation(
                this.observationConvention.getIfAvailable(), // 自定义约定(可为 null)
                new DefaultOrderObservationConvention(),     // 默认约定
                () -> context,
                this.observationRegistry)
            .observe(() -> {
                // ------ 业务逻辑 ------
                String orderId = "ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
                BigDecimal amount = new BigDecimal("99.90");
                context.setOrderId(orderId);
                context.setAmount(amount);
                context.setStatus("CREATED");
                return new OrderResult(orderId, orderNo, amount, "CREATED");
            });
    }

    public record OrderResult(String orderId, String orderNo, BigDecimal amount, String status) {}
}

这段代码有三个关键点:

  1. observation(...) 四参重载:customConvention / defaultConvention / contextSupplier / registrygetIfAvailable() 取用户自定义约定,没有就用默认的;getDefaultConvention() 由文档化枚举提供,所以四参重载可用。
  2. observe(Supplier) 包裹业务:正常返回时自动 stop() 记录指标;抛异常时自动 error(ex)stop(),Timer 上自动出现 error=异常类名 标签。
  3. Context 贯穿:业务逻辑往 Context 里写结果,约定在停止时读取这些结果生成标签。

支付服务(PaymentService)演示异常观测:

java 复制代码
@Service
public class PaymentService {

    private final ObservationRegistry observationRegistry;
    private final ObjectProvider<PaymentObservationConvention> observationConvention;

    public PaymentResult pay(String orderId, String payChannel) {
        PaymentObservationContext context = PaymentObservationContext.builder()
            .operationMetadata(BusinessOperationMetadata.builder()
                .operationType(BusinessOperationType.PAYMENT.value())
                .provider("payment-gateway")
                .build())
            .orderId(orderId)
            .payChannel(payChannel)
            .build();

        return PaymentObservationDocumentation.PAYMENT_CREATE
            .observation(this.observationConvention.getIfAvailable(),
                    new DefaultPaymentObservationConvention(), () -> context, this.observationRegistry)
            .observe(() -> {
                if ("FAIL".equalsIgnoreCase(payChannel)) {   // 故意失败:演示 error 观测
                    throw new IllegalStateException("模拟支付失败: channel=" + payChannel);
                }
                String payNo = "PAY-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
                context.setPayNo(payNo);
                context.setAmount(new BigDecimal("99.90"));
                context.setStatus("PAID");
                return new PaymentResult(payNo, orderId, new BigDecimal("99.90"), "PAID");
            });
    }

    public record PaymentResult(String payNo, String orderId, BigDecimal amount, String status) {}
}

结算服务(CheckoutService)演示嵌套观测

java 复制代码
@Service
public class CheckoutService {

    private final ObservationRegistry observationRegistry;
    private final ObjectProvider<CheckoutObservationConvention> observationConvention;
    private final OrderService orderService;
    private final PaymentService paymentService;

    public CheckoutResult checkout(String orderNo, Long userId, String orderType) {
        CheckoutObservationContext context = CheckoutObservationContext.builder()
            .operationMetadata(BusinessOperationMetadata.builder()
                .operationType(BusinessOperationType.CHECKOUT.value())
                .provider("checkout-service")
                .build())
            .orderNo(orderNo)
            .userId(userId)
            .orderType(orderType)
            .build();

        return CheckoutObservationDocumentation.CHECKOUT
            .observation(this.observationConvention.getIfAvailable(),
                    new DefaultCheckoutObservationConvention(), () -> context, this.observationRegistry)
            .observe(() -> {
                // 在 checkout 的 openScope 内再发起两个观测:
                // 它们自动成为 checkout 的子节点(parent-child 嵌套)
                OrderService.OrderResult order = this.orderService.createOrder(orderNo, userId, orderType);
                PaymentService.PaymentResult payment = this.paymentService.pay(order.orderId(), "WECHAT");
                context.setOrderId(order.orderId());
                context.setPayNo(payment.payNo());
                context.setTotalAmount(payment.amount());
                context.setStatus("SUCCESS");
                return new CheckoutResult(order.orderId(), payment.payNo(), payment.amount(), "SUCCESS");
            });
    }

    public record CheckoutResult(String orderId, String payNo, BigDecimal amount, String status) {}
}

observe() 在回调运行期间持有打开的 scope,期间新建的观测会被 ObservationRegistry 自动关联为当前观测的子节点------这就是嵌套的由来,业务代码零额外成本。

3.7 控制器(Controller)

作用REST 入口,把观测能力暴露成可调用的 HTTP 接口。

java 复制代码
@RestController
@RequestMapping("/api")
public class OrderController {

    private final OrderService orderService;
    private final PaymentService paymentService;
    private final CheckoutService checkoutService;

    public OrderController(OrderService orderService, PaymentService paymentService, CheckoutService checkoutService) {
        this.orderService = orderService;
        this.paymentService = paymentService;
        this.checkoutService = checkoutService;
    }

    @PostMapping("/order/create")
    public OrderService.OrderResult createOrder(@RequestParam String orderNo, @RequestParam Long userId,
            @RequestParam(defaultValue = "NORMAL") String orderType) {
        return this.orderService.createOrder(orderNo, userId, orderType);
    }

    @PostMapping("/order/pay")
    public PaymentService.PaymentResult pay(@RequestParam String orderId,
            @RequestParam(defaultValue = "WECHAT") String payChannel) {
        return this.paymentService.pay(orderId, payChannel);
    }

    @PostMapping("/checkout")
    public CheckoutService.CheckoutResult checkout(@RequestParam String orderNo, @RequestParam Long userId,
            @RequestParam(defaultValue = "NORMAL") String orderType) {
        return this.checkoutService.checkout(orderNo, userId, orderType);
    }
}

4. 运行与验证

4.1 启动应用

bash 复制代码
cd micrometer-boot-demo
mvn spring-boot:run

4.2 触发业务

bash 复制代码
# 下单
curl -X POST "http://localhost:8080/api/order/create?orderNo=NO-001&userId=1001&orderType=NORMAL"

# 支付成功
curl -X POST "http://localhost:8080/api/order/pay?orderId=ORD-TEST001&payChannel=WECHAT"

# 支付失败(演示 error 观测)
curl -X POST "http://localhost:8080/api/order/pay?orderId=ORD-TEST002&payChannel=FAIL"

# 结算(下单+支付嵌套)
curl -X POST "http://localhost:8080/api/checkout?orderNo=NO-002&userId=1002&orderType=VIP"

4.3 查看指标

bash 复制代码
curl "http://localhost:8080/actuator/prometheus"

自动生成的 Timer 指标(低基数标签 + error 标签):

text 复制代码
order_create_seconds_count{business_operation_type="order",business_provider="order-service",order_type="NORMAL",error="none"} 1

payment_create_seconds_count{business_operation_type="payment",business_provider="payment-gateway",pay_channel="FAIL",error="IllegalStateException"} 1
payment_create_seconds_count{business_operation_type="payment",business_provider="payment-gateway",pay_channel="WECHAT",error="none"} 1

checkout_create_seconds_count{business_operation_type="checkout",business_provider="checkout-service",order_type="VIP",error="none"} 1

自定义业务指标(来自 handleronStop):

text 复制代码
business_order_amount_count{order_type="NORMAL"} 1
business_order_amount_sum{order_type="NORMAL"} 99.9

business_payment_amount_count{pay_channel="WECHAT"} 1
business_payment_amount_sum{pay_channel="WECHAT"} 99.9

4.4 Grafana 展示

Grafana 中添加 Prometheus 数据源(http://prometheus:9090),新建 Dashboard 后即可用 PromQL 查询,例如:

sql 复制代码
# 支付失败率
rate(payment_create_seconds_count{error!="none"}[5m])
  / rate(payment_create_seconds_count[5m])

# 订单支付总金额(每分钟)
sum(rate(business_payment_amount_sum[5m]))

5. 总结

本案例把观测五件套完整复刻到业务代码里:

职责
metadata BusinessObservationAttributes / BusinessOperationType / BusinessOperationMetadata 属性名常量与操作元数据
context *ObservationContext 承载业务输入与结果
documentation *ObservationDocumentation 声明观测点、低/高基数 Key
convention *ObservationConvention + Default* 决定指标名与标签
handler *MeterObservationHandler 停止时写业务指标
config BusinessObservationConfiguration 注册 handler Bean

业务代码的改动被压缩到最小,只需 三行固定套路

java 复制代码
Context ctx = XxxContext.builder()...build();
return XxxDocumentation.XXX
    .observation(customConvention, defaultConvention, () -> ctx, registry)
    .observe(() -> { /* 原业务逻辑 */ });

指标、错误统计、链路嵌套、Prometheus/Grafana 对接全部由框架与约定完成,业务与可观测性彻底解耦。

相关推荐
张忠琳2 小时前
【k3s】AutoK3s v0.9.3 Part 1 入口与 CLI 命令模块 — 超深度逐行分析之三
云原生·容器·kubernetes·k3s·autok3s
嘻哈∠※2 小时前
0061基于 SpringBoot 的投稿与稿件处理系统设计与实现
java·spring boot·后端
m0_525724725 小时前
AIOps全链路智能运维架构揭秘
云原生·devops
zhougl9968 小时前
Java Spring Boot 爬虫技术全面介绍
java·spring boot·爬虫
xiaoxiangsiyan9 小时前
企业日常运维高频应用服务全解
运维·网络·云原生·容器·dns
阿里云云原生9 小时前
一次请求只做一次决策:阿里云 AI 网关智能路由如何替大模型应用选模型
云原生
用户31268748772011 小时前
@Async 加了没生效?Spring Boot 异步编程全链路拆解:从线程池到自调用陷阱
java·spring boot
zhangjw3411 小时前
第41篇:微服务入门:架构理念+核心组件,理解微服务本质
微服务·云原生·架构
RobinDevNotes12 小时前
K8s+Ray+vLLM打穿大模型全生命周期(有实践步骤)
人工智能·云原生·容器·kubernetes·生活·vllm
云烟成雨TD13 小时前
Micrometer 系列【25】Spring Boot Actuator | Spring MVC、Spring WebFlux 指标
java·spring boot·micrometer