Spring Boot 监控和管理接口

Spring Boot提供了收集一些系统信息和对系统进行管理的功能,并会把它们以JMX或Http的方式发布出来,用户可以把它们集成到自己的监控系统。需要使用这些功能时需要添加如下依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Endpoint

Spring Boot把一类监控和管理称为一个Endpoint。打开jconsole可以看到发布在org.springframework.boot.Endpoint下的Endpoint。也可以通过浏览器访问/actuator看到通过Http发布的Endpoint。默认情况下绝大部分的Endpoint都会通过JMX发布(shutdown默认不发布),但只有少部分会通过Http发布,所以你通过JMX可以看到发布了很多Endpoint,而通过Http默认将只能看到info和health。每一个Endpoint都对应有一个唯一的id,常见的Endpoint有如下这些:

  • beans :列出应用中所有的Spring bean
  • conditions :列出所有的@ConditionalXXX匹配或不匹配的原因
  • configprops :列出所有的@ConfigurationProperties信息
  • env :列出Spring Environment中的属性信息(application.properties中指定的属性都将加入Environment中)
  • health :列出应用的健康状况
  • httptrace :列出最近100笔Http请求跟踪信息,包括请求信息和响应信息
  • info :列出应用的一些信息
  • loggers :可以查看和配置logger信息
  • metrics :可以查看metrics信息
  • mappings :可以查看路径映射信息
  • scheduledtasks :查看Spring定时任务信息
  • shutdown :可以用来停止应用
  • threaddump :进行Thread dump
  • heapdump :进行heap dump

能够发布哪些将依赖于你的Classpath下拥有哪些Endpoint。关于上面这些Endpoint的介绍请参考https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/html/production-ready-endpoints.html。可以通过management.endpoint.endpointId.enabled指定某个Endpoint是否可用,比如如果需要禁用info Endpoint,则可以指定management.endpoint.info.enabled=false

前面提到几乎所有的Endpoint都会发布到JMX,少数会发布到HTTP,这是由Spring Boot的Endpoint的默认发布策略决定的,可以通过management.endpoints.enabled-by-default来改变默认发布策略,比如默认发布的都改为不发布,可以指定management.endpoints.enabled-by-default=false。但是如果你想把默认不发布的Endpoint通过management.endpoints.enabled-by-default=true指定为发布则是不可行的。基于这种需求得使用更细粒度的控制。Spring Boot分别为JMX和Http发布方式提供了management.endpoints.jmx.exposuremanagement.endpoints.web.exposure,分别包含了include和exclude属性,用于控制需要发布和不发布的Endpoint。假如不想通过JMX方式发布beans和mappings这两个Endpoint,则可以配置management.endpoints.jmx.exposure.exclude=beans,mappings,而如果希望通过Web方式发布beans和mappings这两个Endpoint,则可以配置management.endpoints.web.exposure.include=beans,mappings。如果需要发布所有或者禁用所有,则可以使用*表示。比如需要通过Web方式发布所有的Endpoint,则可以配置management.endpoints.web.exposure.include=*management.endpoints.jmx.exposure.include属性的默认值是*,而management.endpoints.web.exposure.include的默认值是info, health,而exclude默认都是空。所以当你指定了management.endpoints.jmx.exposure.exclude=beans,mappings时发布的Endpoint将只剩下beans和mappings。exclude和include是可以一起使用的,比如需要通过Web方式发布除beans和mappings以外的所有Endpoint时可以如下配置。

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=beans,mappings

当同时指定了include和exclude时,exclude将拥有更高的优先级,即exclude中包含的内容一定会被排除。

Endpoint无参读缓存

Spring Boot会对所有Endpoint的无参数的读操作的结果进行缓存,可以通过cache.time-to-live指定某个Endpoint的无参读操作的缓存时间,比如下面的配置指定了beans这个Endpoint的无参读操作的缓存时间是10秒。

management.endpoint.beans.cache.time-to-live=10s

Web方式发布的Endpoint的路径

Web方式发布的Endpoint的默认的根路径是/actuator,每一个是Endpoint会以Endpoint对应的id发布为二级路径,比如beans这个Endpoint的映射路径是/actuator/beans。可以通过management.endpoints.web.base-path调整根路径,通过management.endpoints.web.path-mapping.xxx调整具体某个Endpoint的路径。比如下面的代码就指定了Endpoint的根路径是/actuator1,loggers Endpoint的路径是loggers1,所以访问loggers Endpoint的最终路径是/actuator1/loggers1

management.endpoints.web.base-path=/actuator1
management.endpoints.web.path-mapping.loggers=loggers1

跨域访问Endpoint Http接口

前面提到了,Spring Boot提供的用于监控的Endpoint会以JMX或Http的方式发布,Http方式发布的都是Rest接口,响应都是JSON,没有界面。如果你提供自己的监控平台,在自己的监控平台需要访问这些监控信息,可能就会遇到浏览器的跨域问题。可以通过management.endpoints.web.cors.allowed-origins指定允许哪个域进行跨域访问。比如如果我们的Spring Boot应用部署在http://localhost:8081,监控平台部署在http://localhost:8080,则以下配置则允许监控平台访问我们Spring Boot应用上发布的Endpoint。

management.endpoints.web.cors.allowed-origins=http://localhost:8080

如果想更精细的控制跨域时只允许某些请求方式,则可以通过management.endpoints.web.cors.allowed-methods进行指定。

management.endpoints.web.cors.allowed-methods=GET,POST

定义自己的Endpoint

如果你需要定义自己的Endpoint,可以定义一个标注了@org.springframework.boot.actuate.endpoint.annotation.Endpoint的Class,然后把它定义为Spring容器中的一个bean。需要通过@Endpoint的id属性指定一个唯一的id,可以通过@ReadOperation@WriteOperation@DeleteOperation标注方法,分别对应GET、POST和DELETE请求。Endpoint的请求路径还是Endpoint的根路径加上具体Endpoint的id,如果如下的Endpoint的默认请求路径将是/actuator/hello

@Component
@Endpoint(id="hello")
public class HelloEndpoint {
    @ReadOperation
    public String hello() {
        return "Hello World";
    }
  
}

使用@Endpoint标注的Endpoint是可以通过JMX和Http方式发布的,如果只期望通过Http发布,可以使用@org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint,如果只期望通过JMX发布,可以使用@org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint

指定Endpoint发布的端口和绑定地址

可能为了安全需要,你不希望Endpoint随着你的应用一起发布,并可对外访问。可以为Endpoint发布指定一个另外的端口,或者绑定另外一个内网ip。比如下面指定了绑定的端口号是8089,地址是127.0.0.1,即对应的Endpoint只能在本机通过localhost或127.0.0.1访问。

management.server.port=8089
management.server.address=127.0.0.1

如果想禁用通过Http方式发布Endpoint,可以设置management.server.port=-1

HealthEndpoint

HealthEndpoint是用来监控应用的健康状态的。其监控是基于org.springframework.boot.actuate.health.HealthIndicator接口的,由其health()返回的org.springframework.boot.actuate.health.Health来反应某个组件的健康状况,Spring Boot已经提供了一些HealthIndicator实现。如果需要实现自己的HealthIndicator,只需要把它定义为一个bean。

@Component
public class HelloHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        if (System.currentTimeMillis()%2==0) {
            return Health.down().withDetail("error", "详细信息,可以是一个对象").build();
        }
        return Health.up().build();
    }
}

在通过/actuator/health查看应用的健康状况时默认只会返回一个总的状态,不会展示详细的每个组件的健康状况。可以通过配置management.endpoint.health.show-details=always返回每个组件的健康状况。

InfoEndpoint

InfoEndpoint用来展示应用的一些信息,它会把容器中所有org.springframework.boot.actuate.info.InfoContributor类型的bean发布的信息收集起来。Spring Boot提供了一个org.springframework.boot.actuate.info.EnvironmentInfoContributor实现,它允许我们把需要展示的应用信息以info.*的形式定义在application.properties文件中。下面的代码中就定义了info信息,包含属性name和app,其中app又是一个对象,包含一个description。访问InfoEndpoint时你会得到这样的信息:{"name":"Spring Boot","app":{"description":"Hello World"}}

info.name=Spring Boot
info.app.description=Hello World

如果有需要也可以实现自己的InfoContributor,并把它定义为bean。下面是一个简单的示例,

@Component
public class HelloInfoContributor implements InfoContributor {
    @Override
    public void contribute(Builder builder) {
        Map<String, Object> detailMap = new HashMap<>();
        detailMap.put("map.key1", "value1");
        detailMap.put("map.key2", "value2");
        detailMap.put("map.key1.key1.1", "value");
    
        Map<String, Object> otherMap = new HashMap<>(detailMap);
        detailMap.put("map.key1.key1.2", otherMap);
    
        builder
            .withDetail("key1", "value1")
            .withDetail("key1.key1.1", "value2")
            .withDetails(detailMap);
    }
}

实现InfoContributor时,如果你需要展示的信息有多层结构,应当把它们包装为一个Map。上面的代码对应的获取info的结果如下:

{
    name: "Spring Boot",
    app: {
        description: "Hello World"
    },
    key1: "value1",
    key1.key1.1: "value2",
    map.key2: "value2",
    map.key1: "value1",
    map.key1.key1.2: {
        map.key2: "value2",
        map.key1: "value1",
        map.key1.key1.1: "value"
    },
    map.key1.key1.1: "value"
}

参考文档