|
此版本仍在开发中,尚未稳定。如需最新的稳定版本,请使用 Spring Framework 7.0.6! |
可观测性支持
Micrometer 定义了一个 支持指标和追踪的观察(Observation)概念。 指标支持提供了一种创建计时器、仪表或计数器的方法,用于收集有关应用程序运行时行为的统计信息。 指标可帮助您跟踪错误率、使用模式、性能等。 追踪提供了跨越应用程序边界的整个系统的整体视图;您可以放大特定的用户请求,并跟踪其在各个应用程序中的完整执行过程。
Spring Framework 对其自身代码库的各个部分进行了监控,以便在配置了 ObservationRegistry 时发布观测数据。
您可以了解更多关于在 Spring Boot 中配置可观测性基础设施的信息。
生成的观测列表
Spring 框架为可观察性提供了多种功能。 如本节开头所述,根据配置,观测可以生成计时器指标和/或跟踪信息。
| 观测名称 | 描述 |
|---|---|
HTTP客户端交换所花费的时间 |
|
在框架级别处理HTTP服务器交换的时间 |
|
消息生产者向目标发送 JMS 消息所花费的时间。 |
|
消息使用者先前接收的JMS消息的处理时间。 |
|
执行 |
| 观测结果使用Micrometer的官方命名约定,但指标名称将自动转换 为监控系统后端首选的格式 (Prometheus、Atlas、Graphite、InfluxDB…)。 |
Micrometer Observation 概念
如果你不熟悉 Micrometer Observation,以下是关于你需要了解的概念的简要概述。
-
Observation是对应用程序中发生的事件的实际记录。这由ObservationHandler实现处理以生成指标或跟踪信息。 -
每个观测都有一个对应的
ObservationContext实现;此类型包含用于为其提取元数据的所有相关信息。 对于 HTTP 服务器观测,上下文实现可以包含 HTTP 请求、HTTP 响应、处理过程中抛出的任何异常等。 -
每个
Observation包含KeyValues个元数据。以HTTP服务器观察为例,这可以是HTTP请求方法、HTTP响应状态等。 这些元数据由ObservationConvention实现提供,它们应声明所支持的ObservationContext类型。 -
KeyValues如果KeyValue元组的可能值数量较少且有界,则被称为“低基数”(HTTP 方法就是一个很好的例子)。 低基数值仅被计入指标中。 相反,“高基数”值是无界的(例如,HTTP 请求 URI),仅被计入追踪中。 -
一个
ObservationDocumentation文档会记录特定领域内的所有观测结果,列出预期的键名称及其含义。
配置观测
全局配置选项位于 ObservationRegistry#observationConfig() 级别。
每个被检测的组件将提供两个扩展点:
-
设置
ObservationRegistry;如果不设置,观测数据将不会被记录,且操作将无效 -
提供自定义的
ObservationConvention以更改默认的观察名称和提取的KeyValues
使用自定义的观察(Observation)约定
以 Spring MVC "http.server.requests" 指标监控为例,结合 ServerHttpObservationFilter 进行说明。
该观测使用了带有 ServerRequestObservationContext 的 ServerRequestObservationConvention;可以在 Servlet 过滤器上配置自定义约定。
如果要自定义观测生成的元数据,可以根据需要扩展 DefaultServerRequestObservationConvention:
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
public class ExtendedServerRequestObservationConvention extends DefaultServerRequestObservationConvention {
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
// here, we just want to have an additional KeyValue to the observation, keeping the default values
return super.getLowCardinalityKeyValues(context).and(custom(context));
}
private KeyValue custom(ServerRequestObservationContext context) {
return KeyValue.of("custom.method", context.getCarrier().getMethod());
}
}
如果你想要完全控制,可以为你感兴趣的观测实现整个约定契约:
import java.util.Locale;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.ServerHttpObservationDocumentation;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.http.server.observation.ServerRequestObservationConvention;
public class CustomServerRequestObservationConvention implements ServerRequestObservationConvention {
@Override
public String getName() {
// will be used as the metric name
return "http.server.requests";
}
@Override
public String getContextualName(ServerRequestObservationContext context) {
// will be used for the trace name
return "http " + context.getCarrier().getMethod().toLowerCase(Locale.ROOT);
}
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
return KeyValues.of(method(context), status(context), exception(context));
}
@Override
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
return KeyValues.of(httpUrl(context));
}
private KeyValue method(ServerRequestObservationContext context) {
// You should reuse as much as possible the corresponding ObservationDocumentation for key names
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod());
}
// status(), exception(), httpUrl()...
private KeyValue status(ServerRequestObservationContext context) {
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus()));
}
private KeyValue exception(ServerRequestObservationContext context) {
String exception = (context.getError() != null ? context.getError().getClass().getSimpleName() : KeyValue.NONE_VALUE);
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, exception);
}
private KeyValue httpUrl(ServerRequestObservationContext context) {
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
}
}
您也可以使用自定义的 ObservationFilter 来实现类似的目标——为观测值添加或删除键值。
过滤器不会取代默认约定,而是作为后处理组件使用。
import io.micrometer.common.KeyValue;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationFilter;
import org.springframework.http.server.observation.ServerRequestObservationContext;
public class ServerRequestObservationFilter implements ObservationFilter {
@Override
public Observation.Context map(Observation.Context context) {
if (context instanceof ServerRequestObservationContext serverContext) {
context.setName("custom.observation.name");
context.addLowCardinalityKeyValue(KeyValue.of("project", "spring"));
String customAttribute = (String) serverContext.getCarrier().getAttribute("customAttribute");
context.addLowCardinalityKeyValue(KeyValue.of("custom.attribute", customAttribute));
}
return context;
}
}
您可以在 ObservationRegistry 上配置 ObservationFilter 个实例。
定时任务的监控
每次执行 @Scheduled 任务时都会创建一个观测(Observation)。
应用程序需要在 ScheduledTaskRegistrar 上配置 ObservationRegistry 以启用观测记录功能。
这可以通过声明一个设置观察注册表(observation registry)的 SchedulingConfigurer Bean 来实现:
import io.micrometer.observation.ObservationRegistry;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
public class ObservationSchedulingConfigurer implements SchedulingConfigurer {
private final ObservationRegistry observationRegistry;
public ObservationSchedulingConfigurer(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setObservationRegistry(this.observationRegistry);
}
}
默认情况下,它使用org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention,后由ScheduledTaskObservationContext支持。
您可以直接在ObservationRegistry上配置自定义实现。
在计划方法执行期间,当前观察结果将在ThreadLocal上下文中或Reactor上下文中恢复(如果计划方法返回Mono或Flux类型)。
默认情况下,将创建以下 KeyValues:
名称 |
描述 |
|
计划执行的Java |
|
持有计划方法的Bean实例的类的规范名称,对于匿名类则为 |
|
执行期间抛出的异常的类名,如果没有发生异常则为 |
|
复制了 |
|
方法执行的结果。可以是 |
JMS 消息传递检测
如果类路径中包含 io.micrometer:micrometer-jakarta9 依赖项,Spring Framework 将使用 Micrometer 提供的 Jakarta JMS 检测功能。
io.micrometer.jakarta9.instrument.jms.JmsInstrumentation 会对 jakarta.jms.Session 进行检测并记录相关的观测数据。
此检测将创建两种类型的观测:
-
当JMS消息发送到代理时为
"jms.message.publish",通常为JmsTemplate。 -
"jms.message.process"当应用程序处理JMS消息时,通常使用带有MessageListener或@JmsListener注解的方法。
目前对于"jms.message.receive"次观察尚无监测手段,因为衡量等待消息接收所花费的时间价值不大。
这种集成通常会监测MessageConsumer#receive个方法调用。但一旦这些调用返回,处理时间将不再被测量,并且跟踪范围无法传播到应用程序中。 |
默认情况下,这两个观测共享同一组可能的 KeyValues:
名称 |
描述 |
|
在消息操作期间抛出的异常的类名(或“none”)。 |
|
复制了 |
|
目标是 |
|
正在执行的JMS操作的名称(值: |
名称 |
描述 |
|
JMS消息的关联ID。 |
|
当前消息发送到的目标目的地的名称。 |
|
消息系统用作消息标识符的值。 |
JMS消息发布检测
当JMS消息发送到代理时,会记录"jms.message.publish"个观测值。
它们用于测量发送消息所花费的时间,并通过传出的JMS消息头传播跟踪信息。
您需要在 JmsTemplate 上配置 ObservationRegistry 以启用观测:
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.JmsTemplate;
public class JmsTemplatePublish {
private final JmsTemplate jmsTemplate;
private final JmsMessagingTemplate jmsMessagingTemplate;
public JmsTemplatePublish(ObservationRegistry observationRegistry, ConnectionFactory connectionFactory) {
this.jmsTemplate = new JmsTemplate(connectionFactory);
// configure the observation registry
this.jmsTemplate.setObservationRegistry(observationRegistry);
// For JmsMessagingTemplate, instantiate it with a JMS template that has a configured registry
this.jmsMessagingTemplate = new JmsMessagingTemplate(this.jmsTemplate);
}
public void sendMessages() {
this.jmsTemplate.convertAndSend("spring.observation.test", "test message");
}
}
默认使用 io.micrometer.jakarta9.instrument.jms.DefaultJmsPublishObservationConvention,由 io.micrometer.jakarta9.instrument.jms.JmsPublishObservationContext 提供支持。
当响应消息从监听器方法返回时,使用 @JmsListener 注解的方法会记录类似的观察结果。
JMS消息处理检测
当应用程序处理JMS消息时,会记录"jms.message.process"个观测值。
它们用于测量处理消息所花费的时间,并通过传入的JMS消息头传播跟踪上下文。
大多数应用程序将使用@JmsListener注解方法机制来处理传入的消息。
您需要确保在专用的JmsListenerContainerFactory上配置了ObservationRegistry:
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
@Configuration
@EnableJms
public class JmsConfiguration {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory, ObservationRegistry observationRegistry) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setObservationRegistry(observationRegistry);
return factory;
}
}
必须提供默认的容器工厂才能启用注解支持,
但请注意,@JmsListener 个注解可以引用特定用途的特定容器工厂Bean。
在所有情况下,只有在容器工厂上配置了观察注册表时,才会记录观测值。
在处理消息时,使用JmsTemplate的情况也会有类似记录,此时是由MessageListener进行处理的。
这类监听器被设置在一个会话回调中的MessageConsumer上(参见JmsTemplate.execute(SessionCallback<T>))。
此观测默认使用 io.micrometer.jakarta9.instrument.jms.DefaultJmsProcessObservationConvention,由 io.micrometer.jakarta9.instrument.jms.JmsProcessObservationContext 提供支持。
HTTP 服务器检测
Servlet和Reactive应用的HTTP服务器交换观察结果使用名称"http.server.requests"创建,
如果使用OpenTelemetry规范,则使用名称"http.server.request.duration"。
Servlet 应用
应用程序需要在它们的应用中配置 org.springframework.web.filter.ServerHttpObservationFilter Servlet 过滤器。
只有当 Exception 未被Web框架处理并传播到Servlet过滤器时,才会将其记录为错误。
通常,由Spring MVC的 @ExceptionHandler 和 ProblemDetail 支持 处理的所有异常都不会被观察机制记录。
您可以在请求处理过程中的任何时刻,自行在 ObservationContext 上设置错误字段:
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.filter.ServerHttpObservationFilter;
@Controller
public class UserController {
@ExceptionHandler(MissingUserException.class)
ResponseEntity<Void> handleMissingUser(HttpServletRequest request, MissingUserException exception) {
// We want to record this exception with the observation
ServerHttpObservationFilter.findObservationContext(request)
.ifPresent(context -> context.setError(exception));
return ResponseEntity.notFound().build();
}
static class MissingUserException extends RuntimeException {
}
}
由于检测是在 Servlet Filter 级别完成的,因此观测范围仅涵盖在此 Filter 之后执行顺序的 Filters 以及请求的处理过程。
通常,Servlet 容器的错误处理在较低级别执行,不会有任何活动的观测或跨度(span)。
对于此用例,需要特定于容器的实现,例如针对 Tomcat 的 org.apache.catalina.Valve;这超出了本项目的范围。 |
默认语义约定
默认使用 org.springframework.http.server.observation.DefaultServerRequestObservationConvention,由 ServerRequestObservationContext 提供支持。
默认情况下,将创建以下 KeyValues:
名称 |
描述 |
|
交换期间抛出的异常的类名,如果没有发生异常,则为 |
|
复制了 |
|
HTTP请求方法的名称,如果不是常见的方法,则为 |
|
HTTP服务器交换的结果。 |
|
HTTP 响应的原始状态码,如果未创建响应,则为 |
|
匹配处理程序的URI模式(如果可用),否则对于3xx响应回退到 |
名称 |
描述 |
|
HTTP 请求 URI。 |
响应式应用
应用程序需要使用 MeterRegistry 配置 WebHttpHandlerBuilder 以启用服务器检测。
这可以在 WebHttpHandlerBuilder 上完成,如下所示:
import io.micrometer.observation.ObservationRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
@Configuration(proxyBeanMethods = false)
public class HttpHandlerConfiguration {
private final ApplicationContext applicationContext;
public HttpHandlerConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public HttpHandler httpHandler(ObservationRegistry registry) {
return WebHttpHandlerBuilder.applicationContext(this.applicationContext)
.observationRegistry(registry)
.build();
}
}
默认使用 org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention,由 ServerRequestObservationContext 提供支持。
只有当 Exception 未被应用程序控制器处理时,才会将其记录为错误。
通常,由 Spring WebFlux 的 @ExceptionHandler 和 ProblemDetail 支持 处理的所有异常都不会被记录到该观察中。
您可以在请求处理过程中的任何时刻,自行在 ObservationContext 上设置错误字段:
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.server.ServerWebExchange;
@Controller
public class UserController {
@ExceptionHandler(MissingUserException.class)
ResponseEntity<Void> handleMissingUser(ServerWebExchange exchange, MissingUserException exception) {
// We want to record this exception with the observation
ServerRequestObservationContext.findCurrent(exchange.getAttributes())
.ifPresent(context -> context.setError(exception));
return ResponseEntity.notFound().build();
}
static class MissingUserException extends RuntimeException {
}
}
默认情况下,将创建以下 KeyValues:
名称 |
描述 |
|
交换期间抛出的异常的类名,如果没有发生异常,则为 |
|
复制了 |
|
HTTP请求方法的名称,如果不是常见的方法,则为 |
|
HTTP服务器交换的结果。 |
|
HTTP 响应的原始状态码,如果未创建响应,则为 |
|
匹配处理程序的URI模式(如果可用),否则对于3xx响应回退到 |
名称 |
描述 |
|
HTTP 请求 URI。 |
HTTP 客户端检测
HTTP 客户端交换观察数据使用名称 "http.client.requests" 为阻塞和响应式客户端创建。
此观察数据测量整个 HTTP 请求/响应交换过程,从建立连接到正文反序列化为止。
与服务器端相对,instrumentation 是直接在客户端实现的,因此唯一需要的步骤是在客户端配置一个 ObservationRegistry。
RestTemplate
应用程序必须在 RestTemplate 实例上配置 ObservationRegistry 以启用检测;否则,观测将为“空操作”。
Spring Boot 将自动配置已设置观察注册表的 RestTemplateBuilder bean。
监控功能默认使用 org.springframework.http.client.observation.ClientRequestObservationConvention,由 ClientRequestObservationContext 提供支持。
名称 |
描述 |
|
HTTP请求方法的名称,如果不是常见的方法,则为 |
|
用于HTTP请求的URI模板,如果没有提供,则为 |
|
从请求URI的主机名派生的客户端名称。 |
|
HTTP响应的原始状态码,如果出现 |
|
HTTP 客户端交换的结果。 |
|
交换期间抛出的异常的类名,如果没有发生异常,则为 |
|
复制了 |
名称 |
描述 |
|
HTTP 请求 URI。 |
RestClient
应用程序必须在 RestClient.Builder 上配置 ObservationRegistry 以启用监控;否则,观测操作将为“无操作”。
监控功能默认使用 org.springframework.http.client.observation.ClientRequestObservationConvention,由 ClientRequestObservationContext 提供支持。
名称 |
描述 |
|
HTTP请求方法的名称,如果无法创建请求则为 |
|
用于HTTP请求的URI模板,如果没有提供,则为 |
|
从请求URI的主机名派生的客户端名称。 |
|
HTTP响应的原始状态码,如果出现 |
|
HTTP 客户端交换的结果。 |
|
交换期间抛出的异常的类名,如果没有发生异常,则为 |
|
复制了 |
名称 |
描述 |
|
HTTP 请求 URI。 |
WebClient
应用程序必须在ObservationRegistry上配置一个WebClient.Builder以启用检测;如果没有,观测将是“无操作”。
Spring Boot将使用已设置观测注册表的WebClient.Builder bean进行自动配置。
监控功能默认使用 org.springframework.web.reactive.function.client.ClientRequestObservationConvention,由 ClientRequestObservationContext 提供支持。
名称 |
描述 |
|
HTTP请求方法的名称,如果不是常见的方法,则为 |
|
用于HTTP请求的URI模板,如果没有提供,则为 |
|
从请求URI的主机名派生的客户端名称。 |
|
HTTP响应的原始状态码,如果出现 |
|
HTTP 客户端交换的结果。 |
|
交换期间抛出的异常的类名,如果没有发生异常,则为 |
|
复制了 |
名称 |
描述 |
|
HTTP 请求 URI。 |
应用程序事件和 @EventListener
Spring 框架不会为 @EventListener 调用 提供观测数据,
因为它们不具备此类监控的正确语义。
默认情况下,事件的发布和处理是同步进行的,并且在同一个线程上执行。
这意味着在任务执行期间,ThreadLocals 和日志上下文将与事件发布者相同。
如果应用程序全局配置了自定义的 ApplicationEventMulticaster,并使用在不同线程上调度事件处理的策略,则不再保证这一点。
所有 @EventListener 方法都将在另一个线程中处理,脱离主事件发布线程。
在这种情况下,Micrometer 上下文传播库 可以帮助传播此类值,并更好地关联事件的处理过程。
应用程序可以配置所选的 TaskExecutor 使用一个装饰任务并传播上下文的 ContextPropagatingTaskDecorator。
要使此功能生效,io.micrometer:context-propagation 库必须存在于类路径中:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.support.ContextPropagatingTaskDecorator;
@Configuration
public class ApplicationEventsConfiguration {
@Bean(name = "applicationEventMulticaster")
public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
// decorate task execution with a decorator that supports context propagation
taskExecutor.setTaskDecorator(new ContextPropagatingTaskDecorator());
eventMulticaster.setTaskExecutor(taskExecutor);
return eventMulticaster;
}
}
同样,如果通过向每个带有 @EventListener 注解的方法添加 @Async,在本地为该异步选择做出决定,
则可以选择一个通过限定符引用它来传播上下文的 TaskExecutor。
给定以下使用专用任务装饰器配置的 TaskExecutor bean 定义:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.support.ContextPropagatingTaskDecorator;
@Configuration
public class EventAsyncExecutionConfiguration {
@Bean(name = "propagatingContextExecutor")
public TaskExecutor propagatingContextExecutor() {
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
// decorate task execution with a decorator that supports context propagation
taskExecutor.setTaskDecorator(new ContextPropagatingTaskDecorator());
return taskExecutor;
}
}
使用@Async注解事件监听器,并配合相应的限定符,可以实现类似上下文传播的效果:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class EmailNotificationListener {
private final Log logger = LogFactory.getLog(EmailNotificationListener.class);
@EventListener(EmailReceivedEvent.class)
@Async("propagatingContextExecutor")
public void emailReceived(EmailReceivedEvent event) {
// asynchronously process the received event
// this logging statement will contain the expected MDC entries from the propagated context
logger.info("email has been received");
}
}