侧边栏壁纸
博主头像
资深人工智能从业者博主等级

行动起来,活在当下

  • 累计撰写 199 篇文章
  • 累计创建 84 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Spring Boot 3.0 要来了,这个特性真心强!

MobotStone
2022-11-16 / 0 评论 / 6 点赞 / 5921 阅读 / 27908 字

前言

Spring 可观察性团队一直致力于为 Spring 应用程序添加可观察性支持,该特性将在 Spring Framework 6 和 Spring Boot 3 中更加简单、易用! 通过可观测性,能更好的了解系统内部运行状态。metrics, logging 和分布式 tracing 之间的相互连通能更好的推断系统的运行状态,以便调试应用程序中的异常、延迟和性能。

即将发布的 Spring Boot3.0.0-RC1 将包含大量的自动配置,用于使用 Micrometer 改进 metrics,并通过 Micrometer tracing (以前称为 Spring Cloud Sleuth)提供新的分布式 tracing 支持。最值得注意的变化是,它将包含对 log 关联的内置支持,W3C上下文传递将是默认传播类型,我们将支持自动传播元数据,以供 tracing 基础设施(称为“远程包裹”)使用,帮助标记观察结果。

Micrometer API 得到了大量的改进,最重要的变化是我们引入了一个新的API:Observation API。

它创建理念是,希望用户使用单一 API 就能检测代码,并从中获得多种信息(例如 metrics, tracing, logging)。

这篇博文详细介绍了你需要了解的API,以及如何使用它来为你的应用程序提供更多可观测信息。

一、Micrometer Observation 怎么运行?

我们需要通过 ObservationRegistry 注册 ObservationHandler 对象。ObservationHandler 仅对支持的 Observation.Context 实现起作用。Context`and可以通过对观察的生命周期事件作出反应来创建计时器、跨度和日志,例如: 观测处理程序仅对受支持的观测实现作出反应。上下文,可以通过对观察的生命周期事件作出反应来创建计时器、跨度和日志,例如:

  • start - 调用 Observation#start() 方法启动 Observation。

  • stop - 调用 Observation#stop() 方法停止 Observation。

  • error - 调用 Observation#error(exception) 方法抛出观测。

  • event - 调用 Observation#event(event) 观察时发生的事。

  • scope started - 调用 Observation#openScope() 方法时,打开了一个 scope。不使用时必须关闭。可以在此创建线程局部变量,这些变量在 scope 关闭时被清除。

  • scope stopped - 调用 Observation.Scope#close() 方法时停止  Observation scope。 每当调用这些方法中的任何一个时,都会调用 ObservationHandler 方法(例如 onStart(T extends Observation.Context ctx) 、onStop(T extends Observation.Context ctx) 等)。要在处理方法之间传递状态,可以使用Observation.Context

Observation 状态图如下:

        Observation           Observation
        Context               Context
Created ----------> Started ----------> Stopped

Observation Scope 状态图如下:

              Observation
              Context
Scope Started ----------> Scope Closed

为了调试生产问题,observation 需要额外的元数据,例如键值对(也称为 tags)。然后,可以使用这些 tag 来查找所需的数据,从而查询 metrics 或分布式 tracing 后端。tag 可以是高基数或低基数。

这是 Micrometer Observation API 的一个示例。

// 创建 ObservationRegistry
ObservationRegistry registry = ObservationRegistry.create();
// 注册 ObservationHandler
registry.observationConfig().observationHandler(new MyHandler());

// 创建 Observation!
Observation.createNotStarted("user.name", registry)
        .contextualName("getting-user-name")
        .lowCardinalityKeyValue("userType", "userType1") // 假设你有3种用户类型
        .highCardinalityKeyValue("userId", "1234") // 假设这是一个任意数
        .observe(() -> log.info("Hello")); // 这是启动观察、打开范围、运行用户代码、关闭范围和停止观察的快捷方式

重要说明高基数意味着一对可能值的数量是无限多的。HTTP URL 就是这样一个键值的好例子(例如 /user/user1234,/user/user2345 等)。低基数意味着键值将具有有限数量的可能值。模板化的 HTTP URL(例如 /user/{userId})就是这样一个键值的好例子。

要将 observation 生命周期操作与 observation 配置(例如名称以及低基数和高基数标记)分离,可以使用 ObservationConvention ,它提供了一种覆盖默认命名约定的简单方法。

二、构建可观测应用

开始的最简单方法是从 https://start.spring.io。确保选择的时 Spring Boot 3.0.0-SNAPSHOT (你可以切换到 RC1 如果他已经发布) 和你喜欢的构建工具。 我们将构建一个 Spring WebMvc 服务端应用程序和一个 RestTemplate 调用服务器的客户端。下面我们从服务端开始。

2.1 WebMvc 服务启动

因为我们想启动 HTTP 服务, 需要选择 org.springframework.boot:spring-boot-starter-web 依赖。 要使用 @Observed 切面创建  observations, 我们需要添加org.springframework.boot:spring-boot-starter-aop 依赖。 要在应用中添加可观测功能,请选择 spring-boot-starter-actuator (并将 Micrometer 添加到 classpath).

  • Metrics

  • 对于 Micrometer metrics 可以使用 Prometheus, 只需要添加io.micrometer:micrometer-registry-prometheus 依赖.

  • Tracing

  • 对于使用 Micrometer Tracing 跟踪 Tracing 上下文传播 , 我们需要选择一个 tracer 桥接 (tracer 用于处理跨度生命周期的库). 我们选择 Zipkin Brave 然后添加 io.micrometer:micrometer-tracing-bridge-brave依赖。

  • 对于延迟可视化, 我们需要以某种格式将完成的 spans 发送到服务器。在我们的案例中我们生成了一个符合 Zipkin-compliant 标准的 span。为此,我们需要添加io.zipkin.reporter2:zipkin-reporter-brave 依赖。

  • Logs

  • 因为我们在 classpath 添加了 Micrometer Tracing, 所以 logs 也是自动关联的 (也就是说,它们包含一个惟一的 trace 标识符). 为了这个演示,我们将日志推送到 Grafana Loki。我们可以通过添加 com.github.loki4j:loki-logback-appender:latest.release 依赖来实现。 | 重要说明 | 如果你不熟悉 tracing,我们需要快速定义两个基本术语。你可以在 span 中包装任何操作。它具有唯一的 span id,并包含计时信息和一些附加的元数据(键-值对)。因为可以从 span 生成子 span,所以整个 span 树形成一个共享相同 trace id(即关联标识符)的 trace。

现在我们需要添加一些配置。我们设置actuator 和metrics用来生成百分位直方图,并重新定义日志模式以包括跟踪和范围标识符。我们将采样概率设置为1.0,以将所有跟踪发送到延迟分析工具。

# /src/main/resources/application.properties

server.port=7654
spring.application.name=server

# All traces should be sent to latency analysis tool
management.tracing.sampling.probability=1.0
management.endpoints.web.exposure.include=prometheus

# For Exemplars to work we need histogram buckets
management.metrics.distribution.percentiles-histogram.http.server.requests=true

# traceID and spanId are predefined MDC keys - we want the logs to include them
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]

因为我们在本地使用 Loki 和 Tempo 运行 Grafana,所以我们配置 loki-logback-appender,将日志发送到 Loki 的本地实例。

<!-- /src/main/resources/logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml" />
    <springProperty scope="context" name="appName" source="spring.application.name"/>

    <appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
        <http>
            <url>http://localhost:3100/loki/api/v1/push</url>
        </http>
        <format>
            <label>
                <pattern>app=${appName},host=${HOSTNAME},traceID=%X{traceId:-NONE},level=%level</pattern>
            </label>
            <message>
                <pattern>${FILE_LOG_PATTERN}</pattern>
            </message>
            <sortByTime>true</sortByTime>
        </format>
    </appender>

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

2.2 WebMvc 服务端代码

是时候编写一些服务器端代码了!我们希望实现应用程序的完全可观察性,包括metrics, tracing 和附加 logging 记录。 首先,我们编写一个控制器,打印 log 并调用 service。

// MyController.java
@RestController
class MyController {

    private static final Logger log = LoggerFactory.getLogger(MyController.class);
    private final MyUserService myUserService;

    MyController(MyUserService myUserService) {
        this.myUserService = myUserService;
    }

    @GetMapping("/user/{userId}")
    String userName(@PathVariable("userId") String userId) {
        log.info("Got a request");
        return myUserService.userName(userId);
    }
}

我们想对 MyUserService#userName 方法进行一些详细跟踪观察。由于添加了AOP支持,我们可以使用@Observed注解。为此,我们可以注册 ObservedAspect bean。

// MyConfiguration.java
@Configuration(proxyBeanMethods = false)
class MyConfiguration {
    // To have the @Observed support we need to register this aspect
    @Bean
    ObservedAspect observedAspect(ObservationRegistry observationRegistry) {
        return new ObservedAspect(observationRegistry);
    }
}
// MyUserService.java
@Service
class MyUserService {
    private static final Logger log = LoggerFactory.getLogger(MyUserService.class);

    private final Random random = new Random();

    // Example of using an annotation to observe methods
    // <user.name> will be used as a metric name
    // <getting-user-name> will be used as a span  name
    // <userType=userType2> will be set as a tag for both metric & span
    @Observed(name = "user.name",
            contextualName = "getting-user-name",
            lowCardinalityKeyValues = {"userType", "userType2"})
    String userName(String userId) {
        log.info("Getting user name for user with id <{}>", userId);
        try {
            Thread.sleep(random.nextLong(200L)); // simulates latency
        }
        catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "foo";
    }
}

有了这个注释就会创建一个 timer、一个 long task timer 和一个span。计时器将命名为user.name,长任务计时器将命名为 user.name.active,而 span 将命名为 getting-user-name。 logs 我们则可以创建一个专门的处理程序,为每个观察记录一些日志信息。

// MyHandler.java
@Component
class MyHandler implements ObservationHandler<Observation.Context> {

    private static final Logger log = LoggerFactory.getLogger(MyHandler.class);

    @Override
    public void onStart(Observation.Context context) {
        log.info("Before running the observation for context [{}], userType [{}]", context.getName(), getUserTypeFromContext(context));
    }

    @Override
    public void onStop(Observation.Context context) {
        log.info("After running the observation for context [{}], userType [{}]", context.getName(), getUserTypeFromContext(context));
    }

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

    private String getUserTypeFromContext(Observation.Context context) {
        return StreamSupport.stream(context.getLowCardinalityKeyValues().spliterator(), false)
                .filter(keyValue -> "userType".equals(keyValue.getKey()))
                .map(KeyValue::getValue)
                .findFirst()
                .orElse("UNKNOWN");
    }
}

注册一个 bean 打开控制器的可观察性。

// MyConfiguration.java
@Configuration(proxyBeanMethods = false)
class MyConfiguration {
    // You must set this manually until this is registered in Boot
    @Bean
    FilterRegistrationBean observationWebFilter(ObservationRegistry observationRegistry) {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new HttpRequestsObservationFilter(observationRegistry));
        filterRegistrationBean.setDispatcherTypes(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.FORWARD,
                DispatcherType.INCLUDE, DispatcherType.REQUEST);
        filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        // We provide a list of URLs that we want to create observations for
        filterRegistrationBean.setUrlPatterns(Collections.singletonList("/user/*"));
        return filterRegistrationBean;
    }
}

2.3 RestTemplate 客户端启动

首先我们添加 spring-boot-starter-web 和 spring-boot-starter-actuator 依赖 运行web服务器并添加 Micrometer 支持。 是时候添加可观察性相关功能了!

  • Metrics

  • 对于 Micrometer metrics 可以使用 Prometheus, 只需要添加 io.micrometer:micrometer-registry-prometheus 依赖。

  • Tracing

  • 对于使用 Micrometer Tracing 跟踪 Tracing 上下文传播,我们需要选择一个 tracer 桥. 我选择 OpenTelemetry 并添加 io.micrometer:micrometer-tracing-bridge-otel依赖。

  • 对于**延迟可视化,**我们需要以某种格式将完成的 spans 发送到服务器。在我们的案例中我们生成了一个符合 OpenZipkin compliant 标准 span, 我们需要添加 io.opentelemetry:opentelemetry-exporter-zipkin 依赖。

  • Logs

  • 添加 com.github.loki4j:loki-logback-appender:latest.release 依赖并将日志发送到 Loki。 现在我们需要添加一些配置。我们添加了与服务器端几乎相同的配置。

# /src/main/resources/application.properties

server.port=6543
spring.application.name=client

# All traces should be sent to latency analysis tool
management.tracing.sampling.probability=1.0
management.endpoints.web.exposure.include=prometheus

# traceID and spanId are predefined MDC keys - we want the logs to include them
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]

Loki Appender 配置看,起来完全相同:

<!-- /src/main/resources/logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml" />
    <springProperty scope="context" name="appName" source="spring.application.name"/>

    <appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
        <http>
            <url>http://localhost:3100/loki/api/v1/push</url>
        </http>
        <format>
            <label>
                <pattern>app=${appName},host=${HOSTNAME},traceID=%X{traceId:-NONE},level=%level</pattern>
            </label>
            <message>
                <pattern>${FILE_LOG_PATTERN}</pattern>
            </message>
            <sortByTime>true</sortByTime>
        </format>
    </appender>

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

2.4 RestTemplate 应用代码

我们利用 RestTemplate 发送一个请求,我们希望实现应用程序的全部可观测性,包括 metrics 和 tracing。 首先,我们使用 RestTemplateBuilder 创建一个 RestTemplate bean。

// MyConfiguration.java
@Configuration(proxyBeanMethods = false)
class MyConfiguration {
    // IMPORTANT! To instrument RestTemplate you must inject the RestTemplateBuilder
    @Bean
    RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}

现在,我们可以编写一个 CommandLineRunner bean,它使用 Observation API包装,并向服务器端发送请求。下面的代码片段更详细地描述了 API 的所有功能。

// MyConfiguration.java
@Configuration(proxyBeanMethods = false)
class MyConfiguration {
    @Bean
    CommandLineRunner myCommandLineRunner(ObservationRegistry registry, RestTemplate restTemplate) {
        Random highCardinalityValues = new Random(); // Simulates potentially large number of values
        List<String> lowCardinalityValues = Arrays.asList("userType1", "userType2", "userType3"); // Simulates low number of values
        return args -> {
            String highCardinalityUserId = String.valueOf(highCardinalityValues.nextLong(100_000));
            // Example of using the Observability API manually
            // <my.observation> is a "technical" name that does not depend on the context. It will be used to name e.g. Metrics
             Observation.createNotStarted("my.observation", registry)
                     // Low cardinality means that the number of potential values won't be big. Low cardinality entries will end up in e.g. Metrics
                    .lowCardinalityKeyValue("userType", randomUserTypePicker(lowCardinalityValues))
                     // High cardinality means that the number of potential values can be large. High cardinality entries will end up in e.g. Spans
                    .highCardinalityKeyValue("userId", highCardinalityUserId)
                     // <command-line-runner> is a "contextual" name that gives more details within the provided context. It will be used to name e.g. Spans
                    .contextualName("command-line-runner")
                     // The following lambda will be executed with an observation scope (e.g. all the MDC entries will be populated with tracing information). Also the observation will be started, stopped and if an error occurred it will be recorded on the observation
                    .observe(() -> {
                        log.info("Will send a request to the server"); // Since we're in an observation scope - this log line will contain tracing MDC entries ...
                        String response = restTemplate.getForObject("http://localhost:7654/user/{userId}", String.class, highCardinalityUserId); // Boot's RestTemplate instrumentation creates a child span here
                        log.info("Got response [{}]", response); // ... so will this line
                    });

        };
    }
}

限制

WebMvc Observability 的 Spring Boot AutoConfiguration 尚未完成。因此,我们需要手动设置。有关详细信息,请参阅此 issue。 为了让 Spring Boot Exemplars AutoConfiguration 正常工作,我们需要等待此 PR 和此 PR 合并。在此之前,我们需要手动创建配置。

2.5 运行

我们已经准备好了这个链接下整个可观测性基础设施的Docker设置。按照以下步骤运行基础架构和两个应用程序。

运行示例

使用 docker-compose 启动他们

$ docker-compose up
1. Prometheus 默认地址:[http://localhost:9090/](http://localhost:9090/)
2. Grafana默认地址:[http://localhost:3000/](http://localhost:3000/)

运行服务器端应用程序。

$ ./mvnw spring-boot:run -pl :server

运行客户端应用程序。

$ ./mvnw spring-boot:run -pl :client

You should see log statements similar to these:2022-10-04T15:04:55.345+02:00  INFO [client,bbe3aea006077640b66d40f3e62f04b9,93b7a150b7e293ef] 92556 --- [           main] com.example.client.ClientApplication     : Will send a request to the server
2022-10-04T15:04:55.385+02:00  INFO [client,bbe3aea006077640b66d40f3e62f04b9,93b7a150b7e293ef] 92556 --- [           main] com.example.client.ClientApplication     : Got response [foo]

打开 Grafana 仪表盘,然后单击 Logs, Traces, Metrics 仪表盘。在这里,您可以选择一个 trace ID(例如 bbe3aea006077640b66d40f3e62f04b9),以从两个应用程序中查找与该 trace ID 对应的所有 logs 和 traces 信息。应该会看到以下与同一 trace ID 相关的日志和traces 相关视图,你将看到在同一时间范围内发生的 metrics。这些指标与 HTTP 请求处理延迟有关。这些来自使用 Micrometer API 的自动收集的 Spring Boot WebMvc 信息。
image-1668596807099
image-1668596822123

  1. 注意 metrics 中的图性,这些是Exemplars 。这些是“特定轨迹,代表在给定时间间隔内进行的测量”。如果单击形状,可以跳到 trace ID 视图查看对应该的 trace.

  2. 单击 trace ID 用 Tempo 查询 要么跳转到自己选择的 trace ID。您将看到以下屏幕。 每个条形代表一个span 。我们可以看到完成每个操作所需的时间。如果单击给定的范围,我们可以看到与该特定操作相关的 tags(键值元数据)和计时信息。

image-1668596835846

这就是相关日志视图在 Loki 中的效果图:

image-1668596851244

如果想查看@Observed 注解的方法 metrics,可以转到 Prometheus 视图并找到user_name Timer。 如果想查看手动创建的 Observation metrics,请转到 Prometheus 视图并找到 my_observation Timer。

2.6 与 AOT 支持一起运行

为了更好地理解 Spring Boot 如何支持 Native,请阅读这篇优秀的博客文章。

构建

要构建应用程序,需要配置 GraalVM 到 path。如果使用 SDKMan 调用如下:

sdk install java 22.2.r17-nik

查看 GraalVM 快速使用。 使用 Maven 构建,你需要启用 native 环境:

$ ./mvnw -Pnative clean package

运行

先运行服务端。

$ ./server/target/server

然后运行客户端。

$ ./client/target/client

客户端日志:

2022-10-10T12:57:17.712+02:00  INFO [client,,] 82009 --- [           main] com.example.client.ClientApplication     : Starting ClientApplication using Java 17.0.4 on marcin-precision5560 with PID 82009 (/home/marcin/repo/observability/blogs/bootRc1/client/target/client started by marcin in /home/marcin/repo/observability/blogs/bootRc1)
2022-10-10T12:57:17.712+02:00  INFO [client,,] 82009 --- [           main] com.example.client.ClientApplication     : No active profile set, falling back to 1 default profile: "default"
2022-10-10T12:57:17.723+02:00  INFO [client,,] 82009 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 6543 (http)
2022-10-10T12:57:17.723+02:00  INFO [client,,] 82009 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-10-10T12:57:17.723+02:00  INFO [client,,] 82009 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.0.23]
2022-10-10T12:57:17.727+02:00  INFO [client,,] 82009 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-10-10T12:57:17.727+02:00  INFO [client,,] 82009 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 15 ms
2022-10-10T12:57:17.731+02:00  WARN [client,,] 82009 --- [           main] i.m.c.i.binder.jvm.JvmGcMetrics          : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2022-10-10T12:57:17.781+02:00  INFO [client,,] 82009 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 15 endpoint(s) beneath base path '/actuator'
2022-10-10T12:57:17.783+02:00  INFO [client,,] 82009 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 6543 (http) with context path ''
2022-10-10T12:57:17.783+02:00  INFO [client,,] 82009 --- [           main] com.example.client.ClientApplication     : Started ClientApplication in 0.077 seconds (process running for 0.079)
2022-10-10T12:57:17.784+02:00  INFO [client,27c1113e4276c4173daec3675f536bf4,e0f2db8b983607d8] 82009 --- [           main] com.example.client.ClientApplication     : Will send a request to the server
2022-10-10T12:57:17.820+02:00  INFO [client,27c1113e4276c4173daec3675f536bf4,e0f2db8b983607d8] 82009 --- [           main] com.example.client.ClientApplication     : Got response [foo]
2022-10-10T12:57:18.966+02:00  INFO [client,,] 82009 --- [nio-6543-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-10-10T12:57:18.966+02:00  INFO [client,,] 82009 --- [nio-6543-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-10-10T12:57:18.966+02:00  INFO [client,,] 82009 --- [nio-6543-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms

服务端日志:

2022-10-10T12:57:07.200+02:00  INFO [server,,] 81760 --- [           main] com.example.server.ServerApplication     : Starting ServerApplication using Java 17.0.4 on marcin-precision5560 with PID 81760 (/home/marcin/repo/observability/blogs/bootRc1/server/target/server started by marcin in /home/marcin/repo/observability/blogs/bootRc1)
2022-10-10T12:57:07.201+02:00  INFO [server,,] 81760 --- [           main] com.example.server.ServerApplication     : No active profile set, falling back to 1 default profile: "default"
2022-10-10T12:57:07.213+02:00  INFO [server,,] 81760 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 7654 (http)
2022-10-10T12:57:07.213+02:00  INFO [server,,] 81760 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-10-10T12:57:07.213+02:00  INFO [server,,] 81760 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.0.23]
2022-10-10T12:57:07.217+02:00  INFO [server,,] 81760 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-10-10T12:57:07.217+02:00  INFO [server,,] 81760 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 16 ms
2022-10-10T12:57:07.222+02:00  WARN [server,,] 81760 --- [           main] i.m.c.i.binder.jvm.JvmGcMetrics          : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2022-10-10T12:57:07.278+02:00  INFO [server,,] 81760 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 15 endpoint(s) beneath base path '/actuator'
2022-10-10T12:57:07.280+02:00  INFO [server,,] 81760 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 7654 (http) with context path ''
2022-10-10T12:57:07.281+02:00  INFO [server,,] 81760 --- [           main] com.example.server.ServerApplication     : Started ServerApplication in 0.086 seconds (process running for 0.088)
2022-10-10T12:57:07.639+02:00  INFO [server,,] 81760 --- [nio-7654-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-10-10T12:57:07.639+02:00  INFO [server,,] 81760 --- [nio-7654-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-10-10T12:57:07.640+02:00  INFO [server,,] 81760 --- [nio-7654-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-10-10T12:57:17.785+02:00  INFO [server,,] 81760 --- [nio-7654-exec-8] com.example.server.MyHandler             : Before running the observation for context [http.server.requests]
2022-10-10T12:57:17.785+02:00  INFO [server,27c1113e4276c4173daec3675f536bf4,9affba5698490e2d] 81760 --- [nio-7654-exec-8] com.example.server.MyController          : Got a request
2022-10-10T12:57:17.820+02:00  INFO [server,,] 81760 --- [nio-7654-exec-8] com.example.server.MyHandler             : After running the observation for context [http.server.requests]

更多信息可以查看 Grafana 的 metrics 和 traces。阅读 Native 支持限制 章节查看为什么不能将日志推送到 Loki。

Native 支持限制

日志还不会被推给 Loki。更多信息请查看:issue 25847。 客户端还需要手动配置 reflect-config.js 。更多信息,请参阅此 PR。

三、总结

在这篇博文中,我们成功地向您介绍了微米可观察性 API 背后的主要概念。我们还向您展示了如何使用 observe API 和  annotations 自定义可观察。您还可以可视化延迟,查看相关日志,并检查来自 Spring Boot 应用程序的指标。 当然还可以通过使用 Spring native 中的 native images 来观察应用程序。

四、下一步

基于社区的反馈,将继续改进的可观察性。并且打算在今年11月GA。

6