spring boot actuator의 health check는 해당 서비스의 라이브 상태만을 체크 하는 것이 아니라 해당 서비스가 사용하고 있는 각종 endpoint들의 라이브 상태도 체크를 한다. 여기서 endpoint라는 것은 해당 서비스가 사용하고 있는 DB, Kafka, Redis, JMS와 같은 외부 인프라 서비스를 의미한다. 간혹 health check 수행 시간에 지연이 발생 하는 경우 어떤 endpoint에서 지연이 발생 하는지 확인이 필요한 경우가 있다. 이번 포스팅에서는 health check 대상 endpoint의 health check 수행 시간을 로그에 기록하도록 설정하는 방법에 대해서 기록한다.
HealthContributer (HealthIndicator)
spring boot actuator의 각 endpoint들의 health check는 HealthContributor(HealthIndicator)를 구현한 구현체 내에서 이루어진다. actuator에서 제공하는 contributor는 org.springframework.boot:spring-boot-actuator-autoconfigure 아티팩트의 org.springframework.boot.actuate.autoconfigure 패키지내에 있는 각 XXXXAutoConfiguration에 의해서 자동 빈으로 등록된다. 이때 빈으로 등록될 contributor 대상은 관련 서비스의 빈 생성 여부에 따라서 정해지는 듯 하다.
자동으로 주입되는 health contributor(indicator)는 spring actuator 문서에서 확인할 수 있다.
Mail contributor 생성에 대한 예를 들어 보겠다.
org.springframework.boot.actuate.autoconfigure.mail 패키지에 있는 MailHealthContributorAutoConfiguration 클래스는 아래와 같다.
@AutoConfiguration(after = MailSenderAutoConfiguration.class)
@ConditionalOnClass(JavaMailSenderImpl.class)
@ConditionalOnBean(JavaMailSenderImpl.class)
@ConditionalOnEnabledHealthIndicator("mail")
public class MailHealthContributorAutoConfiguration
extends CompositeHealthContributorConfiguration<MailHealthIndicator, JavaMailSenderImpl> {
public MailHealthContributorAutoConfiguration() {
super(MailHealthIndicator::new);
}
@Bean
@ConditionalOnMissingBean(name = { "mailHealthIndicator", "mailHealthContributor" })
public HealthContributor mailHealthContributor(Map<String, JavaMailSenderImpl> mailSenders) {
return createContributor(mailSenders);
}
}
클래스 내용을 보면 MailSenderAutoConfiguration 이 수행된 이후에 MailHealthContributorAutoConfiguration이 수행된다.
MailHealthContributorAutoConfiguration이 수행되는 조건은 아래와 같다.
- JavaMailSenderImpl.class 가 존재해야 함 (@ConditionalOnClass(JavaMailSenderImpl.class))
- JavaMailSenderImpl.class 가 빈으로 등록되어 있어야 함. (@ConditionalOnBean(JavaMailSenderImpl.class))
- management.health.mail.enabled 설정이 true 로 설정되어 있어야 함. (@ConditionalOnEnabledHealthIndicator("mail"))
- @ConditionalOnEnabledHealthIndicator(<name>) 의 경우 management.health.<name>.enabled 가 true로 설정되어야 <name>에 해당하는 서비스의 health check가 활성화 된다.
- management.health.<name>.enabled 설정은 디폴트가 true이다.
- 이를 통해서 사용하고 있는 서비스의 health check contributor 대상에서 제외하려면 management.health.<name>.enabled 설정을 false로 지정하면 된다.
MailSenderAutoConfiguration 클래스는 아래와 같다.
@AutoConfiguration
@ConditionalOnClass({ MimeMessage.class, MimeType.class, MailSender.class })
@ConditionalOnMissingBean(MailSender.class)
@Conditional(MailSenderCondition.class)
@EnableConfigurationProperties(MailProperties.class)
@Import({ MailSenderJndiConfiguration.class, MailSenderPropertiesConfiguration.class })
public class MailSenderAutoConfiguration {
...
}
@Import({MailSenderJndiConfiguration.class, MailSenderPropertiesConfiguration.class}) 에 의해서 MailSnederJndiConfiguration.class, MailSenderPropertiesConfiguration.class Configuration이 실행되는데 MailSenderPropertiesConfiguration.class 는 아래와 같다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.mail", name = "host")
class MailSenderPropertiesConfiguration {
@Bean
@ConditionalOnMissingBean(JavaMailSender.class)
JavaMailSenderImpl mailSender(MailProperties properties) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
applyProperties(properties, sender);
return sender;
}
private void applyProperties(MailProperties properties, JavaMailSenderImpl sender) {
sender.setHost(properties.getHost());
if (properties.getPort() != null) {
sender.setPort(properties.getPort());
}
sender.setUsername(properties.getUsername());
sender.setPassword(properties.getPassword());
sender.setProtocol(properties.getProtocol());
if (properties.getDefaultEncoding() != null) {
sender.setDefaultEncoding(properties.getDefaultEncoding().name());
}
if (!properties.getProperties().isEmpty()) {
sender.setJavaMailProperties(asProperties(properties.getProperties()));
}
}
private Properties asProperties(Map<String, String> source) {
Properties properties = new Properties();
properties.putAll(source);
return properties;
}
}
@ConditionalOnProperty(prefix = "spring.mail", name = "host") 에 의해서 spring.mail.host 설정이 되어 있는 경우에 JavaMailSenderImpl 빈이 생성됨을 알 수 있다. MailProperties 클래스를 통해서 JavaMailSenderImpl 에 메일 관련 접속 설정 정보가 셋팅됨을 알 수 있다. 관련 설정 정보는 아래와 같다.
- spring.mail.host
- spring.mail.port
- spring.mail.username
- spring.mail.password
- spring.mail.protocol
- spring.mail.default-encoding
- spring.mail.properties
- spring.mail.jndi-name
위 접속 정보를 통해서 actuator health check시에 mail contributor 에서 접속 테스트를 통해 mail에 대한 health check가 이루어질 것이다.
정리하자면 mail endpoint의 contributor가 생성되는 조건은 아래와 같다.
- spring.mail 관련 properties 설정 (JavaMailSenderImpl 빈 생성)
- 직접 Configuration을 정의하여 JavaMailSender 빈을 생성하는 경우에는 JavaMailSender가 아닌 JavaMailSenderImpl 인스턴스 빈을 생성해야 한다.
- management.health.mail.enabled=true (지정하지 않은 경우 디폴트 true 로 동작한다)
위와 같이 contributor가 생성되는 조건이 충족이 되면 아래 코드가 실행이 된다.
@Bean
@ConditionalOnMissingBean(name = { "mailHealthIndicator", "mailHealthContributor" })
public HealthContributor mailHealthContributor(Map<String, JavaMailSenderImpl> mailSenders) {
return createContributor(mailSenders);
}
위 코드는 MailHealthIndicator를 생성하고 AutoConfiguredHealthContributorRegistry에 등록이 된다.
AutoConfiguredHealthContributorRegistry 에는 health check 대상이 되는 여러 contributors 를 가지고 있고 AutoConfiguredHealthContributorRegistry 는 HealthEndpoint 빈에 등록이 된다. HealthEndpoint 빈은 HealthEndpointAutoConfiguration에 의해서 생성이 되는데 HealthEndpointAutoConfiguration은 아래와 같다.
@AutoConfiguration
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class)
@EnableConfigurationProperties(HealthEndpointProperties.class)
@Import({ HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class,
HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class })
public class HealthEndpointAutoConfiguration {
}
HealthEndpoint 빈은 HealthEndpointConfiguration 에서 생성되는데 코드는 아래와 같다.
@Bean
@ConditionalOnMissingBean
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups,
HealthEndpointProperties properties) {
return new HealthEndpoint(registry, groups, properties.getLogging().getSlowIndicatorThreshold());
}
위 코드에서 주입되는 HealthEndpointProperties 클래스를 보면 management.endpoint.health.xxx 설정 처리하는데 properties.getLogging().getSlowIndicatorThreshold() 호출은 management.endpoint.health.logging.slow-indicator-threshold에 설정된 값을 전달한다는 것을 알 수 있다. 이 설정 값은 각 contributor의 health check 소요 시간에 대한 임계치 역할을 한다.
HealthEndpoint 클래스는 HealthEndpointSupport<HealthContributor, HealthComponent> 클래스를 상속 받는데 actuator health check 요청시 HealthEndpointSupport 클래스에서 각 contributor들을 순회하면서 health 정보를 가져 온다. HealthEndpointSupport 클래스의 getLoggedHealth 호출을 통해서 contributor의 health 정보를 가져오는데 getLoggedHealth 코드는 아래와 같다.
private T getLoggedHealth(C contributor, String name, boolean showDetails) {
Instant start = Instant.now();
try {
//contributor의 doHealthCheck가 호출된다.
return getHealth(contributor, showDetails);
}
finally {
//로그 레벨이 WARN으로 지정되어 있고 slow-indicator-logging-threshold 가 설정되어 있다면 수행
if (logger.isWarnEnabled() && this.slowIndicatorLoggingThreshold != null) {
Duration duration = Duration.between(start, Instant.now());
//health check 수행 시간이 slow-indicator-logging-threshold 시간을 넘어선 경우
//로그를 생성한다. (slow-indicator-logging-threshold 디폴트는 10초이다)
if (duration.compareTo(this.slowIndicatorLoggingThreshold) > 0) {
String contributorClassName = contributor.getClass().getName();
Object contributorIdentifier = (!StringUtils.hasLength(name)) ? contributorClassName
: contributorClassName + " (" + name + ")";
logger.warn(LogMessage.format("Health contributor %s took %s to respond", contributorIdentifier,
DurationStyle.SIMPLE.print(duration)));
}
}
}
}
finally 블록에서 로그 레벨과 해당 contributor의 health check 수행 시간에 따라서 로그를 기록한다.
Actuator contributor health check 수행 시간 로그 기록
sprint boot actuator에서 연결된 각 contributor의 health check 수행 시간을 로그에 기록하기 위한 조건은 아래와 같다.
- 로그 레벨은 WARN 이상 지정
- contributor의 health check 수행 시간이 slow-indicator-logging-threshold 시간을 초과
properties 설정은 아래와 같다.
management:
endpoint:
health:
logging:
slow-indicator-threshold: 1ms
logging:
level:
org.springframework.boot.actuate: warn
contributor의 health check 소요 시간이 1ms 이상 소요되면 해당 contributor의 health check 소요 시간이 로그에 기록되도록 설정하였다.
위와 같이 설정 후 /actuator/health 를 호출하면 아래와 같이 로그가 생성 됨을 확인할 수 있다.
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.cloud.health.RefreshScopeHealthIndicator (refreshScope) took 1ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.data.redis.RedisHealthIndicator (redis) took 219ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.cloud.stream.binder.kafka.KafkaBinderHealthIndicator (binders/kafka) took 8ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator (db) took 18ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.jms.JmsHealthIndicator (jms) took 107ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.mail.MailHealthIndicator (mail) took 281ms to respond
Actuator contributor health check 대상에서 제외하기
actuator contributor health check 대상 에서 제외하는 설정은 아래와 같다.
management:
health:
jms:
enabled: false
endpoint:
health:
logging:
slow-indicator-threshold: 1ns
management.health.<name>.enabled=false 로 설정하면 해당 <name>에 해당하는 서비스의 health check는 수행하지 않는다.
<name>에 해당 하는 목록은 spring actuator 문서에서 확인 가능하다.
대부분의 contributor health check 소요 시간이 로그에 기록 되도록 slow-indicator-threshold 설정은 1ns(nano second)로 설정하였다.
위와 같이 설정 후 /actuator/health 를 호출하면 아래와 같이 로그가 생성 됨을 확인 할 수 있다.
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.cloud.health.RefreshScopeHealthIndicator (refreshScope) took 1ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.data.redis.RedisHealthIndicator (redis) took 203ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.cloud.stream.binder.kafka.KafkaBinderHealthIndicator (binders/kafka) took 21ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.system.DiskSpaceHealthIndicator (diskSpace) took 0ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.health.PingHealthIndicator (ping) took 0ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator (db) took 19ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.boot.actuate.mail.MailHealthIndicator (mail) took 298ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthContributor$$Lambda$3495/0x00000008014898a0 (discoveryComposite/discoveryClient) took 0ms to respond
org.springframework.boot.actuate.health.HealthEndpointSupport | Health contributor org.springframework.cloud.config.client.ConfigServerHealthIndicator (clientConfigServer) took 0ms to respond
위 로그를 보면 jms contributor의 health check는 로그에 기록되지 않아 health check에서 제외 되었 음을 알 수 있다.
'스프링부트' 카테고리의 다른 글
spring boot JMS activemq connection factory (0) | 2023.09.28 |
---|---|
Spring Boot AutoConfiguration 간단 모듈 만들어보기 (0) | 2023.09.24 |
spring boot websocket (웹소켓) (0) | 2023.09.20 |
spring boot application startup tracking (0) | 2023.09.17 |
Spring boot embedded server 의 default 포트 변경 (0) | 2023.09.16 |
댓글