可觀測性支援
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");
}
}