Spring启动,日志失败健康检查

发布于 2025-01-11 06:09:30 字数 584 浏览 0 评论 0原文

我们在 kubernetes 中使用 spring-boot(2.5.6)

我们使用的许多依赖项包括健康检查,例如 RedisHealthIndicator、CouchbaseHealthIndicator 等。

当这些健康检查之一失败并且整个应用程序健康状况失败时,kubernetes 会重新启动 pod。

然而,没有任何迹象表明失败的原因,Spring 不会记录运行状况检查失败,而是依靠运行状况检查本身来记录消息。 内置健康检查的情况并非如此。

所以从外面看,kubernetes 似乎“无缘无故”地杀死了这个 pod,我们必须假设这是健康检查

Spring 是否有一个“健康检查更改事件”,以便我可以记录哪个 bean 失败了?

或者以其他方式跟踪个人健康状况的“下降”状态

https:/ /github.com/spring-projects/spring-boot/issues/22632 这个问题是类似的,但他们明确声明他们不会记录失败

We use spring-boot(2.5.6) in kubernetes

Many of the dependencies we use include healthchecks for instance RedisHealthIndicator,CouchbaseHealthIndicator etc

When one of these health checks fails and the overall application health fails, the pod is restarted by kubernetes.

However there is no indication why it failed, spring does not log health check failures, instead relying on the Healthcheck itself to log a message.
Which is not the case for the built in health checks.

So from the outside it appears that kubernetes has killed this pod for 'no reason' and we have to assume it was the health check

Does spring have a 'health check change event' so that I can log which bean has failed?

Or otherwise track the 'down' state of the health on an individual basis

https://github.com/spring-projects/spring-boot/issues/22632
This issue is similar but they explicitly state they will not log failures

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

能怎样 2025-01-18 06:09:30

我自己也曾为此奋斗过一段时间。我不确定他们为什么对记录健康故障采取这种立场,但更糟糕的是当前的实现对于尝试注入这种功能非常不友好。

最后,我决定解决的问题是包装健康贡献者,以便我可以在他们报告未启动时记录消息。包装器本身非常简单:

public class LoggingHealthIndicator implements HealthIndicator {
    private static final Logger LOG = LoggerFactory.getLogger(LoggingHealthIndicator.class);
    private final String name;
    private final HealthIndicator delegate;

    public LoggingHealthIndicator(final String name, final HealthIndicator delegate) {
        this.name = name;
        this.delegate = delegate;
    }

    @Override
    public Health health() {
        final Health health = delegate.health();

        if (!Status.UP.equals(health.getStatus())) {
            if (health.getDetails() == null || health.getDetails().isEmpty()) {
                LOG.error("Health check '{}' {}", name, health.getStatus());
            }
            else {
                LOG.error("Health check '{}' {}: {}", name, health.getStatus(), health.getDetails());
            }
        }

        return health;
    }
}

您当然可以做任何您想做的事情;引发应用程序事件,进一步调整您记录的时间和内容等。随您喜欢。

就实际使用而言,这就是有点烦人的地方。它涉及用我们自己的增强版本替换 HealthContributorRegistry

    /**
     * Replicated from {@link org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration}.
     *
     * Note that we lose the {@link org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration.AdaptedReactiveHealthContributors},
     * since it is private. Technically its private to the package-scoped class it's a child of, so we lose twice really.
     */
    @Bean
    @SuppressWarnings("JavadocReference")
    public HealthContributorRegistry healthContributorRegistry(final Map<String, HealthContributor> contributors, final HealthEndpointGroups groups) {
        return new LoggingHealthContributorRegistry(contributors, groups.getNames());
    }    

public class LoggingHealthContributorRegistry extends AutoConfiguredHealthContributorRegistryCopy {

    private static HealthContributor loggingContributor(final Entry<String, HealthContributor> entry) {
        return loggingContributor(entry.getKey(), entry.getValue());
    }

    private static HealthContributor loggingContributor(final String name, final HealthContributor contributor) {
        if (contributor instanceof HealthIndicator){
            return new LoggingHealthIndicator(name, (HealthIndicator)contributor);
        }
        return contributor;
    }

    public LoggingHealthContributorRegistry(Map<String, HealthContributor> contributors, Collection<String> groupNames) {
        // The constructor does not use `registerContributor` on the input map entries
        super(contributors.entrySet().stream().collect(Collectors.toMap(Entry::getKey, LoggingHealthContributorRegistry::loggingContributor)),
              groupNames);
    }

    @Override
    public void registerContributor(String name, HealthContributor contributor) {
        super.registerContributor(name, loggingContributor(name, contributor));
    }
}

关于 AutoConfiguredHealthContributorRegistryCopy 的说明:它实际上只是 AutoConfiguredHealthContributorRegistry 类的副本,该类恰好是包范围的,因此不可继承(除非您不介意玩包)游戏)

I've fought with this awhile myself. I'm not sure why they've taken that stance on logging health failures, but what is worse is the current implementation is incredibly unfriendly to try and inject that kind of functionality into.

In the end, the work around I settled on involved wrapping the health contributors so that I can log messages if they report not-up. The wrapper itself is pretty simple:

public class LoggingHealthIndicator implements HealthIndicator {
    private static final Logger LOG = LoggerFactory.getLogger(LoggingHealthIndicator.class);
    private final String name;
    private final HealthIndicator delegate;

    public LoggingHealthIndicator(final String name, final HealthIndicator delegate) {
        this.name = name;
        this.delegate = delegate;
    }

    @Override
    public Health health() {
        final Health health = delegate.health();

        if (!Status.UP.equals(health.getStatus())) {
            if (health.getDetails() == null || health.getDetails().isEmpty()) {
                LOG.error("Health check '{}' {}", name, health.getStatus());
            }
            else {
                LOG.error("Health check '{}' {}: {}", name, health.getStatus(), health.getDetails());
            }
        }

        return health;
    }
}

You could of course do whatever you want; Raise an application event, further tweak when and what you log, etc. As fancy as you like.

As far as making it actually used, that's where it gets a little annoying. It involves replacing the HealthContributorRegistry with our own enhanced version.

    /**
     * Replicated from {@link org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration}.
     *
     * Note that we lose the {@link org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration.AdaptedReactiveHealthContributors},
     * since it is private. Technically its private to the package-scoped class it's a child of, so we lose twice really.
     */
    @Bean
    @SuppressWarnings("JavadocReference")
    public HealthContributorRegistry healthContributorRegistry(final Map<String, HealthContributor> contributors, final HealthEndpointGroups groups) {
        return new LoggingHealthContributorRegistry(contributors, groups.getNames());
    }    

public class LoggingHealthContributorRegistry extends AutoConfiguredHealthContributorRegistryCopy {

    private static HealthContributor loggingContributor(final Entry<String, HealthContributor> entry) {
        return loggingContributor(entry.getKey(), entry.getValue());
    }

    private static HealthContributor loggingContributor(final String name, final HealthContributor contributor) {
        if (contributor instanceof HealthIndicator){
            return new LoggingHealthIndicator(name, (HealthIndicator)contributor);
        }
        return contributor;
    }

    public LoggingHealthContributorRegistry(Map<String, HealthContributor> contributors, Collection<String> groupNames) {
        // The constructor does not use `registerContributor` on the input map entries
        super(contributors.entrySet().stream().collect(Collectors.toMap(Entry::getKey, LoggingHealthContributorRegistry::loggingContributor)),
              groupNames);
    }

    @Override
    public void registerContributor(String name, HealthContributor contributor) {
        super.registerContributor(name, loggingContributor(name, contributor));
    }
}

A note about AutoConfiguredHealthContributorRegistryCopy: it's literally just a copy of the AutoConfiguredHealthContributorRegistry class that happens to be package-scoped and so isn't inheritable (unless you don't mind playing package games)

忘羡 2025-01-18 06:09:30

这是我的决定,仅记录 /actuator/health 的 UP 状态

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.actuate.endpoint.ApiVersion;
    import org.springframework.boot.actuate.endpoint.SecurityContext;
    import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
    import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
    import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
    import org.springframework.boot.actuate.health.HealthComponent;
    import org.springframework.boot.actuate.health.HealthEndpoint;
    import org.springframework.boot.actuate.health.HealthEndpointGroups;
    import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
    import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
    import org.springframework.boot.actuate.health.Status;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;
    
    @Slf4j
    @Component
    @EndpointWebExtension(endpoint = HealthEndpoint.class)
    public class LoggingReactiveHealthEndpointWebExtension extends ReactiveHealthEndpointWebExtension {
    
      private final ObjectMapper objectMapper;
    
    
      public LoggingReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry,
          HealthEndpointGroups groups, ObjectMapper objectMapper) {
        super(registry, groups, null);
        this.objectMapper = objectMapper;
      }
    
      @Override
      public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
          WebServerNamespace serverNamespace, SecurityContext securityContext, boolean showAll,
          String... path) {
        Mono<WebEndpointResponse<? extends HealthComponent>> health = super.health(apiVersion,
            serverNamespace, securityContext, showAll, path);
    
        return health.doOnNext(it -> {
          if (it.getBody().getStatus() != Status.UP) {
            log.error("Health status: {}, {}", it.getBody().getStatus(), mapToString(it.getBody()));
          }
        });
      }
    
      public <T> String mapToString(T object) {
        try {
          return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
          log.error("Can not convert Object: {} to String", object.getClass().getName(), e);
          return null;
        }
      }
    }

It is my decision for logging only not UP status of /actuator/health

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.actuate.endpoint.ApiVersion;
    import org.springframework.boot.actuate.endpoint.SecurityContext;
    import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
    import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
    import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
    import org.springframework.boot.actuate.health.HealthComponent;
    import org.springframework.boot.actuate.health.HealthEndpoint;
    import org.springframework.boot.actuate.health.HealthEndpointGroups;
    import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
    import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
    import org.springframework.boot.actuate.health.Status;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;
    
    @Slf4j
    @Component
    @EndpointWebExtension(endpoint = HealthEndpoint.class)
    public class LoggingReactiveHealthEndpointWebExtension extends ReactiveHealthEndpointWebExtension {
    
      private final ObjectMapper objectMapper;
    
    
      public LoggingReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry,
          HealthEndpointGroups groups, ObjectMapper objectMapper) {
        super(registry, groups, null);
        this.objectMapper = objectMapper;
      }
    
      @Override
      public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
          WebServerNamespace serverNamespace, SecurityContext securityContext, boolean showAll,
          String... path) {
        Mono<WebEndpointResponse<? extends HealthComponent>> health = super.health(apiVersion,
            serverNamespace, securityContext, showAll, path);
    
        return health.doOnNext(it -> {
          if (it.getBody().getStatus() != Status.UP) {
            log.error("Health status: {}, {}", it.getBody().getStatus(), mapToString(it.getBody()));
          }
        });
      }
    
      public <T> String mapToString(T object) {
        try {
          return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
          log.error("Can not convert Object: {} to String", object.getClass().getName(), e);
          return null;
        }
      }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文