可觀測性支援
Micrometer 定義了“觀測 (Observation)”概念,可實現應用程式中的指標和跟蹤。指標支援提供了一種建立計時器、測量儀或計數器的方法,用於收集有關應用程式執行時行為的統計資訊。指標可以幫助您跟蹤錯誤率、使用模式、效能等。跟蹤提供整個系統的整體檢視,跨越應用程式邊界;您可以放大特定的使用者請求,並跟蹤它們在應用程式中的整個完成過程。
如果配置了 ObservationRegistry,Spring Framework 會檢測其自身程式碼庫的各個部分以釋出觀測。您可以瞭解更多關於在 Spring Boot 中配置可觀測性基礎設施的資訊。
已產生的觀測列表
Spring Framework 為可觀測性檢測了各種功能。如本節開頭所述,觀測可以根據配置生成計時器指標和/或跟蹤。
| 觀測名稱 | 描述 |
|---|---|
HTTP 客戶端交換所花費的時間 |
|
框架級別 HTTP 伺服器交換的處理時間 |
|
訊息生產者傳送 JMS 訊息到目標所花費的時間。 |
|
訊息消費者先前接收到的 JMS 訊息的處理時間。 |
|
|
| 觀測使用 Micrometer 的官方命名約定,但指標名稱將自動轉換為監控系統後端(Prometheus、Atlas、Graphite、InfluxDB…)首選的格式。 |
Micrometer 觀測概念
如果您不熟悉 Micrometer 觀測,以下是您應該瞭解的概念的簡要總結。
-
Observation是應用程式中實際發生事件的記錄。它由ObservationHandler實現處理,以生成指標或跟蹤。 -
每個觀測都有一個相應的
ObservationContext實現;此型別儲存提取其元資料的所有相關資訊。對於 HTTP 伺服器觀測,上下文實現可以儲存 HTTP 請求、HTTP 響應、處理期間丟擲的任何異常等等。 -
每個
Observation都包含KeyValues元資料。對於 HTTP 伺服器觀測,這可能是 HTTP 請求方法、HTTP 響應狀態等等。此元資料由ObservationConvention實現貢獻,這些實現應宣告它們支援的ObservationContext型別。 -
如果
KeyValue元組的可能值數量有限且有界(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 任務的執行都會建立一個觀測。應用程式需要在 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 訊息傳遞檢測
如果 io.micrometer:micrometer-jakarta9 依賴項位於類路徑上,Spring Framework 將使用 Micrometer 提供的 Jakarta JMS 檢測。io.micrometer.jakarta9.instrument.jms.JmsInstrumentation 會檢測 jakarta.jms.Session 並記錄相關的觀測。
此檢測將建立 2 種類型的觀測
-
當 JMS 訊息傳送到代理時,通常使用
JmsTemplate,記錄"jms.message.publish"。 -
當 JMS 訊息由應用程式處理時,通常使用
MessageListener或@JmsListener註釋方法,記錄"jms.message.process"。
目前沒有針對 "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 用於特定目的。在所有情況下,只有在容器工廠上配置了觀測登錄檔時才會記錄觀測。
當訊息由 MessageListener 處理時,JmsTemplate 會記錄類似的觀測。此類監聽器在會話回撥中設定在 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 過濾器級別完成的,因此觀測範圍僅涵蓋在此之後排序的過濾器以及請求的處理。通常,Servlet 容器錯誤處理在較低級別執行,並且不會有任何活動的觀測或跨度。對於此用例,需要特定於容器的實現,例如 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 請求/響應交換,從建立連線到正文反序列化。與伺服器端不同,檢測直接在客戶端實現,因此唯一需要的步驟是在客戶端上配置 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
應用程式必須在 WebClient.Builder 上配置 ObservationRegistry 以啟用檢測;否則,觀測將是“空操作”。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 呼叫提供觀測,因為它們沒有適合此類檢測的語義。預設情況下,事件釋出和處理是同步且在同一執行緒上完成的。這意味著在任務執行期間,ThreadLocal 和日誌上下文將與事件釋出者相同。
如果應用程式全域性配置了一個自定義的 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");
}
}