可观测性支持
Micrometer 定义了一个 可观测性概念,可在应用程序中同时启用 Metrics 和 Traces。Metrics 支持提供了一种创建计时器、量规或计数器的方法,用于收集应用程序运行时行为的统计信息。Metrics 可以帮助您跟踪错误率、使用模式、性能等。Traces 提供整个系统的整体视图,跨越应用程序边界;您可以放大特定用户请求,并跟踪它们在应用程序中的完整完成过程。
如果配置了 ObservationRegistry
,Spring Framework 会在其自身代码库的各个部分进行检测,以发布可观测性数据。您可以了解更多关于在 Spring Boot 中配置可观测性基础设施的信息。
生成的观测数据列表
Spring Framework 对各种功能进行了检测,以实现可观测性。如本节开头所述,观测数据可以根据配置生成定时器 Metrics 和/或 Traces。
观测数据名称 | 描述 |
---|---|
HTTP 客户端交换所花费的时间 |
|
Framework 级别 HTTP 服务器交换的处理时间 |
|
消息生产者向目标发送 JMS 消息所花费的时间。 |
|
消息消费者先前接收到的 JMS 消息的处理时间。 |
|
|
观测数据使用 Micrometer 的官方命名约定,但 Metrics 名称将自动转换为监控系统后端偏好的格式 (Prometheus, Atlas, Graphite, InfluxDB 等)。 |
Micrometer Observation 概念
如果您不熟悉 Micrometer Observation,这里快速总结了一些您应该了解的概念。
-
Observation
是应用程序中发生事件的实际记录。这由ObservationHandler
实现处理,以生成指标或跟踪。 -
每个观测数据都有一个相应的
ObservationContext
实现;这种类型包含所有相关信息,用于提取其元数据。对于 HTTP 服务器观测数据,上下文实现可以包含 HTTP 请求、HTTP 响应、处理过程中抛出的任何异常等。 -
每个
Observation
都包含KeyValues
元数据。对于 HTTP 服务器观测数据,这可以是 HTTP 请求方法、HTTP 响应状态等。此元数据由ObservationConvention
实现贡献,它们应该声明它们支持的ObservationContext
类型。 -
如果
KeyValue
元组可能值的数量很少且有限,则称KeyValues
为“低基数”(HTTP 方法是一个很好的例子)。低基数值仅用于指标。相反,“高基数”值是无界的(例如,HTTP 请求 URI),仅用于跟踪。 -
ObservationDocumentation
记录特定领域中的所有观测数据,列出了预期的键名及其含义。
配置观测数据
全局配置选项可在 ObservationRegistry#observationConfig()
级别获得。每个检测组件将提供两个扩展点
-
设置
ObservationRegistry
;如果未设置,将不会记录观测数据并作为无操作处理 -
提供自定义
ObservationConvention
以更改默认观测数据名称和提取的KeyValues
使用自定义观测数据约定
以使用 ServerHttpObservationFilter
对 Spring MVC 的 "http.server.requests" 指标进行检测为例。该观测数据使用带有 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 任务检测
对于每个 @Scheduled
任务的执行,都会创建一个 Observation。应用程序需要在 ScheduledTaskRegistrar
上配置 ObservationRegistry
以启用观测数据的记录。这可以通过声明一个设置观测数据注册中心的 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 消息传递检测
如果 classpath 中存在 io.micrometer:micrometer-jakarta9
依赖项,Spring Framework 将使用 Micrometer 提供的 Jakarta JMS 检测。io.micrometer.jakarta9.instrument.jms.JmsInstrumentation
对 jakarta.jms.Session
进行检测并记录相关的观测数据。
此检测将创建 2 种类型的观测数据
-
当 JMS 消息发送到 broker 时创建
"jms.message.publish"
观测数据,通常使用JmsTemplate
。 -
当 JMS 消息由应用程序处理时创建
"jms.message.process"
观测数据,通常使用MessageListener
或带有@JmsListener
注解的方法。
目前没有针对 "jms.message.receive" 观测数据的检测,因为测量等待消息接收所花费的时间价值不大。这种集成通常会检测 MessageConsumer#receive 方法调用。但一旦这些调用返回,就不会测量处理时间,并且跟踪范围也无法传播到应用程序。 |
默认情况下,这两种观测数据共享同一组可能的 KeyValues
名称 |
描述 |
|
消息操作期间抛出的异常类名(或 "none")。 |
|
重复 |
|
目标是否为 |
|
正在执行的 JMS 操作名称(值: |
名称 |
描述 |
|
JMS 消息的关联 ID。 |
|
发送当前消息到的目标名称。 |
|
消息系统用作消息标识符的值。 |
JMS 消息发布检测
当 JMS 消息发送到 broker 时,会记录 "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。在所有情况下,只有在容器工厂上配置了观测数据注册中心时,才会记录观测数据。
当消息由 MessageListener
处理时,使用 JmsTemplate
也会记录类似的观测数据。此类监听器在会话回调(参见 JmsTemplate.execute(SessionCallback<T>)
)中设置在 MessageConsumer
上。
默认情况下,此观测数据使用 io.micrometer.jakarta9.instrument.jms.DefaultJmsProcessObservationConvention
,并由 io.micrometer.jakarta9.instrument.jms.JmsProcessObservationContext
提供支持。
HTTP 服务器检测
HTTP 服务器交换观测数据以名称 "http.server.requests"
创建,适用于 Servlet 和响应式应用程序。
Servlet 应用
应用程序需要在其应用程序中配置 org.springframework.web.filter.ServerHttpObservationFilter
Servlet 过滤器。默认情况下,它使用 org.springframework.http.server.observation.DefaultServerRequestObservationConvention
,并由 ServerRequestObservationContext
提供支持。
只有当 Exception
未被 web 框架处理并冒泡到 Servlet 过滤器时,才会将观测数据记录为错误。通常,Spring MVC 的 @ExceptionHandler
和ProblemDetail
支持处理的所有异常都不会随观测数据一起记录。您可以在请求处理的任何时候,自行在 ObservationContext
上设置 error 字段
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 过滤器级别完成的,因此观测范围仅涵盖在此之后排序的过滤器以及请求的处理。通常,Servlet 容器的错误处理在较低级别执行,不会有任何活动的观测数据或 span。对于此用例,需要特定于容器的实现,例如 Tomcat 的 org.apache.catalina.Valve ;这超出了本项目范围。 |
默认情况下,会创建以下 KeyValues
名称 |
描述 |
|
交换期间抛出的异常类名,如果没有发生异常则为 |
|
重复 |
|
HTTP 请求方法的名称,如果不是已知方法则为 |
|
HTTP 服务器交换的结果。 |
|
HTTP 响应原始状态码,如果未创建响应则为 |
|
匹配 handler 的 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
未被应用程序 Controller 处理时,才会将观测数据记录为错误。通常,Spring WebFlux 的 @ExceptionHandler
和ProblemDetail
支持处理的所有异常都不会随观测数据一起记录。您可以在请求处理的任何时候,自行在 ObservationContext
上设置 error 字段
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 响应原始状态码,如果未创建响应则为 |
|
匹配 handler 的 URI 模式(如果可用),对于 3xx 响应回退到 |
名称 |
描述 |
|
HTTP 请求 URI。 |
HTTP 客户端检测
HTTP 客户端交换观测数据以名称 "http.client.requests"
创建,适用于阻塞和响应式客户端。此观测数据测量整个 HTTP 请求/响应交换,从连接建立到请求体反序列化。与服务器端不同,检测直接在客户端实现,因此唯一需要的步骤是在客户端上配置 ObservationRegistry
。
RestTemplate
应用程序必须在 RestTemplate
实例上配置 ObservationRegistry
以启用检测;否则,观测数据将作为 "no-ops"(无操作)处理。Spring Boot 会自动配置已设置观测数据注册中心的 RestTemplateBuilder
bean。
默认情况下,检测使用 org.springframework.http.client.observation.ClientRequestObservationConvention
,并由 ClientRequestObservationContext
提供支持。
名称 |
描述 |
|
HTTP 请求方法的名称,如果不是已知方法则为 |
|
用于 HTTP 请求的 URI 模板,如果未提供则为 |
|
从请求 URI 主机派生的客户端名称。 |
|
HTTP 响应原始状态码, |
|
HTTP 客户端交换的结果。 |
|
交换期间抛出的异常类名,如果没有发生异常则为 |
|
重复 |
名称 |
描述 |
|
HTTP 请求 URI。 |
RestClient
应用程序必须在 RestClient.Builder
上配置 ObservationRegistry
以启用检测;否则,观测数据将作为 "no-ops"(无操作)处理。
默认情况下,检测使用 org.springframework.http.client.observation.ClientRequestObservationConvention
,并由 ClientRequestObservationContext
提供支持。
名称 |
描述 |
|
HTTP 请求方法的名称,如果无法创建请求则为 |
|
用于 HTTP 请求的 URI 模板,如果未提供则为 |
|
从请求 URI 主机派生的客户端名称。 |
|
HTTP 响应原始状态码, |
|
HTTP 客户端交换的结果。 |
|
交换期间抛出的异常类名,如果没有发生异常则为 |
|
重复 |
名称 |
描述 |
|
HTTP 请求 URI。 |
WebClient
应用程序必须在 WebClient.Builder
上配置 ObservationRegistry
以启用检测;否则,观测数据将作为 "no-ops"(无操作)处理。Spring Boot 会自动配置已设置观测数据注册中心的 WebClient.Builder
bean。
默认情况下,检测使用 org.springframework.web.reactive.function.client.ClientRequestObservationConvention
,并由 ClientRequestObservationContext
提供支持。
名称 |
描述 |
|
HTTP 请求方法的名称,如果不是已知方法则为 |
|
用于 HTTP 请求的 URI 模板,如果未提供则为 |
|
从请求 URI 主机派生的客户端名称。 |
|
HTTP 响应原始状态码, |
|
HTTP 客户端交换的结果。 |
|
交换期间抛出的异常类名,如果没有发生异常则为 |
|
重复 |
名称 |
描述 |
|
HTTP 请求 URI。 |
应用事件和 @EventListener
Spring Framework 不为@EventListener
调用贡献 Observation,因为它们不具备此类检测的正确语义。默认情况下,事件发布和处理是同步的,并且在同一线程上完成。这意味着在该任务执行期间,ThreadLocals 和日志上下文将与事件发布者相同。
如果应用程序全局配置了一个自定义的 ApplicationEventMulticaster
,并且其策略是在不同的线程上调度事件处理,那么这种情况就不再适用。所有 @EventListener
方法将在不同的线程上处理,而不是在主要的事件发布线程上。在这种情况下,Micrometer Context Propagation library 可以帮助传播这些值并更好地关联事件的处理。应用程序可以配置所选的 TaskExecutor
来使用一个 ContextPropagatingTaskDecorator
,它会装饰任务并传播上下文。为了使其工作,io.micrometer:context-propagation
库必须存在于 classpath 中
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");
}
}