可觀察性支援
Micrometer 定義了一個 Observation 概念,可以在應用程式中同時啟用 Metrics 和 Traces。Metrics 支援提供了一種建立計時器、量表或計數器的方法,用於收集有關應用程式執行時行為的統計資訊。 Metrics 可以幫助您追蹤錯誤率、使用模式、效能等等。 Traces 提供了整個系統的整體視圖,跨越應用程式邊界;您可以放大特定的使用者請求,並追蹤它們在應用程式中的完整完成過程。
如果配置了 ObservationRegistry
,Spring 框架會檢測自身程式碼庫的各個部分以發布 observation。您可以進一步了解 在 Spring Boot 中配置可觀察性基礎架構。
產生的 Observation 列表
Spring 框架檢測了各種功能以實現可觀察性。如本節 開頭所述,observations 可以根據配置產生計時器 Metrics 和/或 Traces。
Observation 名稱 | 描述 |
---|---|
HTTP 客户端交換所花費的時間 |
|
框架層級 HTTP 伺服器交換的處理時間 |
|
訊息生產者將 JMS 訊息發送到目的地所花費的時間。 |
|
先前由訊息消費者接收的 JMS 訊息的處理時間。 |
|
|
Observations 正在使用 Micrometer 的官方命名慣例,但 Metrics 名稱將自動轉換為 監控系統後端偏好的格式 (Prometheus、Atlas、Graphite、InfluxDB…)。 |
Micrometer Observation 概念
如果您不熟悉 Micrometer Observation,以下是您應該了解的概念的快速摘要。
-
Observation
是應用程式中發生的某件事的實際記錄。它由ObservationHandler
實作處理,以產生 metrics 或 traces。 -
每個 observation 都有對應的
ObservationContext
實作;此類型保存用於提取其元資料的所有相關資訊。在 HTTP 伺服器 observation 的情況下,context 實作可以保存 HTTP 請求、HTTP 回應、處理期間拋出的任何例外等等。 -
每個
Observation
都保存KeyValues
元資料。在 HTTP 伺服器 observation 的情況下,這可以是 HTTP 請求方法、HTTP 回應狀態等等。此元資料由ObservationConvention
實作貢獻,這些實作應宣告它們支援的ObservationContext
類型。 -
如果
KeyValue
tuple 的可能值數量較少且有界限(HTTP 方法就是一個很好的例子),則KeyValues
被稱為「低基數」。低基數值僅貢獻給 metrics。相反,「高基數」值是無界的(例如,HTTP 請求 URI),並且僅貢獻給 traces。 -
ObservationDocumentation
記錄特定網域中的所有 observations,列出預期的金鑰名稱及其含義。
配置 Observations
全域配置選項在 ObservationRegistry#observationConfig()
層級可用。每個檢測元件將提供兩個擴充點
-
設定
ObservationRegistry
;如果未設定,則不會記錄 observations,並且將為 no-ops -
提供自訂
ObservationConvention
以變更預設 observation 名稱和提取的KeyValues
使用自訂 Observation 慣例
讓我們以 Spring MVC "http.server.requests" metrics 檢測與 ServerHttpObservationFilter
為例。此 observation 使用具有 ServerRequestObservationContext
的 ServerRequestObservationConvention
;自訂慣例可以在 Servlet 篩選器上配置。如果您想自訂使用 observation 產生的元資料,您可以擴充 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());
}
}
如果您想要完全控制,您可以為您感興趣的 observation 實作整個慣例契約
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
達成類似的目標 – 為 observation 新增或移除金鑰值。篩選器不會取代預設慣例,而是用作後處理元件。
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
以啟用 observation 的記錄。這可以透過宣告設定 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
上配置自訂實作。在排程方法執行期間,目前的 observation 會在 ThreadLocal
context 或 Reactor context(如果排程方法傳回 Mono
或 Flux
類型)中還原。
預設情況下,會建立以下 KeyValues
名稱 |
描述 |
|
排程執行的 Java |
|
保存排程方法的 bean 實例的類別的正規名稱,或匿名類別的 |
|
執行期間拋出的例外的類別名稱,如果沒有發生例外,則為 |
|
重複 |
|
方法執行的結果。可以是 |
JMS 訊息傳輸檢測
如果類別路徑上有 io.micrometer:micrometer-jakarta9
依賴項,Spring 框架會使用 Micrometer 提供的 Jakarta JMS 檢測。io.micrometer.jakarta9.instrument.jms.JmsInstrumentation
檢測 jakarta.jms.Session
並記錄相關的 observations。
此檢測將建立 2 種類型的 observations
-
"jms.message.publish"
,當 JMS 訊息傳送到 broker 時,通常使用JmsTemplate
。 -
"jms.message.process"
,當 JMS 訊息由應用程式處理時,通常使用MessageListener
或@JmsListener
註解方法。
目前沒有 "jms.message.receive" observations 的檢測,因為測量等待接收訊息所花費的時間沒有太大價值。此類整合通常會檢測 MessageConsumer#receive 方法呼叫。但是一旦這些呼叫傳回,處理時間就不會被測量,並且追蹤作用域無法傳播到應用程式。 |
預設情況下,這兩種 observations 共用同一組可能的 KeyValues
名稱 |
描述 |
|
訊息傳輸操作期間拋出的例外的類別名稱(或 "none")。 |
|
重複 |
|
目的地是否為 |
|
正在執行的 JMS 操作的名稱(值: |
名稱 |
描述 |
|
JMS 訊息的關聯 ID。 |
|
目前訊息傳送到的目的地的名稱。 |
|
訊息系統用作訊息識別符的值。 |
JMS 訊息發布檢測
當 JMS 訊息傳送到 broker 時,會記錄 "jms.message.publish"
observations。它們測量發送訊息所花費的時間,並使用外發 JMS 訊息標頭傳播追蹤資訊。
您需要在 JmsTemplate
上配置 ObservationRegistry
以啟用 observations
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
支援。
當回應訊息從 listener 方法傳回時,也會使用 @JmsListener
註解方法記錄類似的 observations。
JMS 訊息處理檢測
當 JMS 訊息由應用程式處理時,會記錄 "jms.message.process"
observations。它們測量處理訊息所花費的時間,並使用傳入 JMS 訊息標頭傳播追蹤 context。
大多數應用程式將使用 @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;
}
}
需要預設容器 factory 才能啟用註解支援,但請注意,@JmsListener
註解可以參考特定用途的特定容器 factory bean。在所有情況下,只有在容器 factory 上配置了 observation registry 時,才會記錄 Observations。
當訊息由 MessageListener
處理時,也會使用 JmsTemplate
記錄類似的 observations。此類 listeners 設定在會話回呼中的 MessageConsumer
上(請參閱 JmsTemplate.execute(SessionCallback<T>)
)。
此 observation 預設使用 io.micrometer.jakarta9.instrument.jms.DefaultJmsProcessObservationConvention
,並由 io.micrometer.jakarta9.instrument.jms.JmsProcessObservationContext
支援。
HTTP 伺服器檢測
對於 Servlet 和 Reactive 應用程式,使用名稱 "http.server.requests"
建立 HTTP 伺服器交換 observations。
Servlet 應用程式
應用程式需要在其應用程式中配置 org.springframework.web.filter.ServerHttpObservationFilter
Servlet 篩選器。預設情況下,它使用 org.springframework.http.server.observation.DefaultServerRequestObservationConvention
,並由 ServerRequestObservationContext
支援。
只有當 Exception
未被 Web 框架處理並冒泡到 Servlet 篩選器時,才會將 observation 記錄為錯誤。通常,Spring MVC 的 @ExceptionHandler
和 ProblemDetail
支援 處理的所有例外都不會與 observation 一起記錄。您可以在請求處理期間的任何時間,自行在 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 ;這超出了本專案的範圍。 |
預設情況下,會建立以下 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"
建立,適用於阻塞式和反應式客户端。與伺服器端對應項不同,檢測直接在客户端中實作,因此唯一需要的步驟是在客户端上配置 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
呼叫 貢獻觀察,因為它們沒有此類檢測的正確語意。預設情況下,事件發布和處理是同步完成的,並且在同一個執行緒上完成。這表示在該任務執行期間,ThreadLocals 和日誌記錄上下文將與事件發布者相同。
如果應用程式全域配置了自訂的 ApplicationEventMulticaster
,其策略是在不同的執行緒上排程事件處理,則情況不再如此。所有 @EventListener
方法都將在不同的執行緒上處理,在主事件發布執行緒之外。在這些情況下,Micrometer Context Propagation 程式庫 可以幫助傳播這些值,並更好地關聯事件的處理。應用程式可以配置選定的 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");
}
}