Observability Support
Micrometer 在应用程序中定义了一个 Observation concept that enables both Metrics and Traces。度量支持提供了一种创建计时器、测量器或计数器的方法,用于收集有关应用程序运行时行为的统计信息。度量可以帮助你跟踪错误率、使用模式、性能等。跟踪提供整个系统的一个整体视图,跨越应用程序边界;你可以深入研究特定的用户请求,并跟踪它们在各个应用程序中的整个完成情况。 如果配置了`ObservationRegistry`,Spring Framework 会对自己的代码库的各个部分进行检测以发布观测结果。你可以了解更多有关[在 Spring Boot 中配置可观测性基础设施](https://spring.io/docs/spring-boot/docs/current/reference/html/actuator.html#actuator.metrics)的信息。
List of produced Observations
Spring Framework 检测各种功能以进行观测。如at the beginning of this section所述,观测可以根据配置生成计时器度量和/或跟踪。
Observation name | Description |
---|---|
用于 HTTP 客户端交换的时间 |
|
在 Framework 层处理 HTTP 服务器交换的时间 |
|
由消息生产者将 JMS 消息发送到目标所花的时间。 |
|
对消息使用者先前接收的 JMS 消息进行处理的时间。 |
|
执行 |
观察结果使用 Micrometer 的官方命名约定,但指标名称将自动转换 to the format preferred by the monitoring system backend(Prometheus、Atlas、Graphite、InfluxDB…)。 |
Micrometer Observation concepts
如果您不熟悉 Micrometer 观察,以下是对您应该了解的概念的快速总结。
-
Observation
是实际记录应用程序中发生的事情。这由ObservationHandler
实现处理,以生成度量标准或跟踪。 -
每个观察都有一个相应的
ObservationContext
实现;此类型包含用于提取其元数据的相关信息。在 HTTP 服务器观察的情况下,上下文实现可以保存 HTTP 请求、HTTP 响应、处理过程中引发的任何异常,等等。 -
每个
Observation
保存KeyValues
元数据。在 HTTP 服务器观察的情况下,这可以是 HTTP 请求方法、HTTP 响应状态等等。此元数据由ObservationConvention
实现提供,这些实现应声明它们支持的ObservationContext
类型。 -
如果
KeyValue
元组(HTTP 方法是一个很好的示例)的可能值数量少且有界,则称KeyValues
为“低基数”。仅将低基数值放入度量标准中。相反,“高基数”值是无界的(例如,HTTP 请求 URI),仅放入跟踪中。 -
ObservationDocumentation
记录特定域中的所有观察,列出预期的键名及其含义。
Configuring Observations
全局配置选项在“ObservationRegistry#observationConfig()”级别可用。每个检测的组件将提供两个扩展点:
-
设置
ObservationRegistry
;如果未设置,则不会记录观察,并且这些观察将是空操作 -
提供自定义
ObservationConvention
来更改默认观察名称和提取的KeyValues
Using custom Observation conventions
我们以 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 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();
}
@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 tasks instrumentation
为 xref:integration/scheduling.adoc#scheduling-enable-annotation-support[each execution of an @Scheduled
任务创建了观察。应用程序需要在 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);
}
}
默认情况下,它使用由 ScheduledTaskObservationContext
支持的 org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention
。你可以在 ObservationRegistry
上直接配置自定义实现。在执行计划方法时,当前观察结果会还原到 ThreadLocal
上下文或 Reactor 上下文(如果计划方法返回 Mono
或 Flux
类型)。
默认情况下,将创建以下 KeyValues
:
Name |
Description |
|
已计划执行的 Java |
|
包含计划方法的 bean 实例的类的规范名称,或匿名的类的 |
|
执行过程中抛出的异常的类名称,或如果未发生异常则为 |
|
复制 |
|
方法执行的结果。可能是 |
JMS messaging instrumentation
如果 classpath 中存在 io.micrometer:micrometer-jakarta9
依赖项,Spring 框架会使用 Micrometer 提供的 Jakarta JMS 检测器。io.micrometer.jakarta9.instrument.jms.JmsInstrumentation
检测器检测 jakarta.jms.Session
并在相关观察结果中记录该检测器。
此检测器将创建 2 种类型的观察结果:
-
"jms.message.publish"
当 JMS 消息发送给代理时,通常使用JmsTemplate
。 -
"jms.message.process"
当应用程序处理 JMS 消息时,通常使用带有MessageListener
或@JmsListener
注释的方法。
目前没有用于 |
默认情况下,这两个观察结果共享同一组可能的 KeyValues
:
Name |
Description |
|
消息传递操作期间引发的异常的类名(或 "none")。 |
|
复制 |
|
目的地是 |
|
正在执行的 JMS 操作的名称(值: |
Name |
Description |
|
JMS 消息的相关性 ID。 |
|
当前消息发送到的目的地的名称。 |
|
消息系统用作消息标识符的值。 |
JMS message Publication instrumentation
当 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.JmsPublishObservationContext
支持的 io.micrometer.jakarta9.instrument.jms.DefaultJmsPublishObservationConvention
。
JMS message Processing instrumentation
当应用程序处理 JMS 消息时,将记录 "jms.message.process"
观察结果。它们会衡量处理消息所需的时间,并使用传出的 JMS 消息标头传播跟踪上下文。
大多数应用程序将使用 @JmsListener
annotated methods 机制来处理传入消息。你需要确保在专用 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;
}
}
一个 default container factory is required to enable the annotation support, 但请注意,@JmsListener
注释可以引用特定目的的特定容器工厂 bean。在所有情况下,仅当在容器工厂上配置了观察注册中心时才记录观察结果。
当 MessageListener
处理消息时,会在 JmsTemplate
中记录类似的观察结果。此类侦听器是在会话回调中的 MessageConsumer
上设置的(请参见 JmsTemplate.execute(SessionCallback<T>)
)。
默认情况下,此观察结果使用由 io.micrometer.jakarta9.instrument.jms.JmsProcessObservationContext
支持的 io.micrometer.jakarta9.instrument.jms.DefaultJmsProcessObservationConvention
。
HTTP Server instrumentation
HTTP 服务器交换观察结果使用 Servlet 和 Reactive 应用程序的名称 "http.server.requests"
创建。
Servlet applications
应用程序需要在其应用程序中配置 org.springframework.web.filter.ServerHttpObservationFilter
Servlet 过滤器。默认情况下,它使用由 ServerRequestObservationContext
支持的 org.springframework.http.server.observation.DefaultServerRequestObservationConvention
。
这只会将观测结果记录为错误,如果 web 框架没有处理`Exception`,并且已上升到 Servlet 过滤器。通常,Spring MVC 的`@ExceptionHandler`和ProblemDetail
support处理的所有异常都不会记录在观测结果中。你可以在请求处理过程中的任何时候自行设置`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 之后指定的 Filter 以及请求处理。通常,Servlet 容器错误处理是在较低级别执行的,且不会有任何活动观测或范围。对于此用例,需要一个特定于容器的实现,例如 |
默认情况下,将创建以下 KeyValues
:
Name |
Description |
|
交换期间引发的异常的类名,或 |
|
复制 |
|
HTTP 请求方法的名称或 |
|
HTTP 服务器交换的结果。 |
|
HTTP 响应原始状态码,如果没有创建响应,则为 |
|
匹配处理程序的 URI 模式的模式,如果可用,则设置为 |
Name |
Description |
|
HTTP request URI. |
Reactive applications
应用程序需要使用 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
和 xref:web.adoc#webflux-ann-rest-exceptions[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
:
Name |
Description |
|
交换期间引发的异常的类名,或 |
|
复制 |
|
HTTP 请求方法的名称或 |
|
HTTP 服务器交换的结果。 |
|
HTTP 响应原始状态码,如果没有创建响应,则为 |
|
匹配处理程序的 URI 模式的模式,如果可用,则设置为 |
Name |
Description |
|
HTTP request URI. |
HTTP Client Instrumentation
HTTP 客户端交换观察结果使用名称 "http.client.requests"
为阻塞和反应客户端创建。与服务器对应项不同,仪表化直接在客户端中实现,因此唯一必需的步骤是在客户端上配置 ObservationRegistry
。
RestTemplate
应用程序必须在 RestTemplate
实例上配置 ObservationRegistry
以启用仪表化;如果没有它,观察结果就是“无操作”。Spring Boot 将自动配置已设置观察结果注册表的 RestTemplateBuilder
bean。
仪表化默认情况下使用 org.springframework.http.client.observation.ClientRequestObservationConvention
,该约定由 ClientRequestObservationContext
提供支持。
Name |
Description |
|
HTTP 请求方法的名称或 |
|
用于 HTTP 请求的 URI 模板,如果没有提供,则为 |
|
从请求 URI 主机派生的客户端名称。 |
|
HTTP 响应原始状态代码或在 |
|
HTTP 客户端交换结果。 |
|
交换期间引发的异常的类名,或 |
|
复制 |
Name |
Description |
|
HTTP request URI. |
RestClient
应用程序必须在 RestClient.Builder
上配置 ObservationRegistry
以启用仪表化;如果没有它,观察结果就是“无操作”。
仪表化默认情况下使用 org.springframework.http.client.observation.ClientRequestObservationConvention
,该约定由 ClientRequestObservationContext
提供支持。
Name |
Description |
|
HTTP 请求方法名称或在无法创建请求的情况下为 |
|
用于 HTTP 请求的 URI 模板,如果没有提供,则为 |
|
从请求 URI 主机派生的客户端名称。 |
|
HTTP 响应原始状态代码或在 |
|
HTTP 客户端交换结果。 |
|
交换期间引发的异常的类名,或 |
|
复制 |
Name |
Description |
|
HTTP request URI. |
WebClient
应用程序必须在 WebClient
构建器上配置 ObservationRegistry
以启用仪表化;如果没有它,观察结果就是“无操作”。Spring Boot 将自动配置已设置观察结果注册表的 WebClient.Builder
bean。
仪表化默认情况下使用 org.springframework.web.reactive.function.client.ClientRequestObservationConvention
,该约定由 ClientRequestObservationContext
提供支持。
Name |
Description |
|
HTTP 请求方法的名称或 |
|
用于 HTTP 请求的 URI 模板,如果没有提供,则为 |
|
从请求 URI 主机派生的客户端名称。 |
|
HTTP 响应原始状态代码或在 |
|
HTTP 客户端交换结果。 |
|
交换期间引发的异常的类名,或 |
|
复制 |
Name |
Description |
|
HTTP request URI. |
Application Events and @EventListener
Spring 框架不为 xref:core/beans/context-introduction.adoc#context-functionality-events-annotation[@EventListener
调用提供观察,因为它们没有此类检测的正确语义。默认情况下,事件发布和处理是同步进行的,并且在同一线程上。这意味着在执行该任务期间,ThreadLocals 和日志记录上下文将与事件发布方相同。
如果应用程序通过一种将事件处理计划到不同线程中的策略来全局配置一个自定义 ApplicationEventMulticaster
,则这将不再成立。所有 @EventListener
方法都将在不同的线程中进行处理,而不是主事件发布线程。在这些情况下, Micrometer Context Propagation library 可以帮助传播此类值并更好地关联事件处理。应用程序可以配置所选 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
。给定以下 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");
}
}