Using Spring Boot Actuator with Spring Cloud Stream

I figured I’d share a quick post on how to use Spring Boot Actuator with Spring Cloud Stream microservices as it might be useful for others (And me when I want to set this up again in a month! ?). This allows you to make simple GET requests to see config, state, and stats about your bindings and channels for monitoring or troubleshooting purposes.

  1. Include spring-boot-starter-actuator and spring-boot-starter-web in your pom for your Cloud Stream microservice (you can also add them via Spring Initializr if you’re using that).
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
  1. Enable in your application config. You can include items one at a time (like bindings below or use the “*” to include anything available). Don’t expose all of this in production without researching it more!
spring:
  jackson:
    serialization:
      indent-output: true
management:
  endpoints:
    web:
      exposure:
        include: 
        - bindings
        - "*"
server:
  port: 8081
  1. Start your app and run a query!
    A few that I find useful:

Example response for my input binding (http://localhost:8081/actuator/bindings/input)

{
  "bindingName" : "input",
  "name" : "input",
  "group" : "consumerGroup",
  "pausable" : false,
  "state" : "running",
  "extendedInfo" : {
    "bindingDestination" : "SolaceConsumerDestination{queueName='input.consumerGroup'}",
    "ExtendedConsumerProperties" : {
      "autoStartup" : true,
      "concurrency" : 5,
      "instanceCount" : 1,
      "maxAttempts" : 3,
      "backOffInitialInterval" : 1000,
      "backOffMaxInterval" : 10000,
      "backOffMultiplier" : 2.0,
      "defaultRetryable" : true,
      "extension" : {
        "prefix" : "",
        "provisionDurableQueue" : true,
        "provisionSubscriptionsToDurableQueue" : true,
        "queueAccessType" : 0,
        "queuePermission" : 2,
        "queueDiscardBehaviour" : null,
        "queueMaxMsgRedelivery" : null,
        "queueMaxMsgSize" : null,
        "queueQuota" : null,
        "queueRespectsMsgTtl" : null,
        "anonymousGroupPostfix" : "anon",
        "polledConsumerWaitTimeInMillis" : 100,
        "requeueRejected" : false,
        "queueAdditionalSubscriptions" : [ "topic/>", "a/*/c" ],
        "autoBindDmq" : false,
        "provisionDmq" : true,
        "dmqAccessType" : 0,
        "dmqPermission" : 2,
        "dmqDiscardBehaviour" : null,
        "dmqMaxMsgRedelivery" : null,
        "dmqMaxMsgSize" : null,
        "dmqQuota" : null,
        "dmqRespectsMsgTtl" : null,
        "republishedMsgTtl" : null
      }
    }
  },
  "input" : true
}

Hope that’s helpful!
-Marc

Thanks, Mark. Great article.
One quick question, any idea why the health is UNKNOWN?

curl http://localhost:8080/actuator/health

{

“status”: “UP”,
  “components”: {
    “binders”: {
      “status”: “UNKNOWN”,
      “components”: {
        “solace”: {
          “status”: “UNKNOWN”
        }
      }
    },
    “discoveryComposite”: {
      “description”: “Discovery Client not initialized”,
      “status”: “UNKNOWN”,
      “components”: {
        “discoveryClient”: {
          “description”: “Discovery Client not initialized”,
          “status”: “UNKNOWN”
        }
      }
    },
    “diskSpace”: {
      “status”: “UP”,
      “details”: {
        “total”: 97888145408,
        “free”: 75725651968,
        “threshold”: 10485760,
        “exists”: true
      }
    },
    “ping”: {
      “status”: “UP”
    },
    “reactiveDiscoveryClients”: {
      “description”: “Discovery Client not initialized”,
      “status”: “UNKNOWN”,
      “components”: {
        “Simple Reactive Discovery Client”: {
          “description”: “Discovery Client not initialized”,
          “status”: “UNKNOWN”
        }
      }
    },
    “refreshScope”: {
      “status”: “UP”
    }
  }
}

application.properties
spring.cloud.stream.bindings.articleConsumeSolace-in-0.destination=articles

spring.cloud.stream.bindings.articleConsumeSolace-in-0.group=nonexclusive
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.durable-subscription=true
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.provisionSubscriptionsToDurableQueue=false
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.provisionDurableQueue=false
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.queueNamePrefix=
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.useFamiliarityInQueueName=false
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.useDestinationEncodingInQueueName=false
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.useGroupNameInQueueName=false
spring.cloud.stream.solace.bindings.articleConsumeSolace-in-0.consumer.content-type=application/json

articleError-out-0

spring.cloud.stream.bindings.articleError-out-0.destination=articles/error
spring.cloud.stream.bindings.articleError-out-0.group=nonexclusive
spring.cloud.stream.solace.bindings.articleError-out-0.producer.provisionSubscriptionsToDurableQueue=false
spring.cloud.stream.solace.bindings.articleError-out-0.producer.provisionDurableQueue=false
spring.cloud.stream.solace.bindings.articleError-out-0.producer.queueNamePrefix=
spring.cloud.stream.solace.bindings.articleError-out-0.producer.useFamiliarityInQueueName=false
spring.cloud.stream.solace.bindings.articleError-out-0.producer.useDestinationEncodingInQueueName=false
spring.cloud.stream.solace.bindings.articleError-out-0.producer.useGroupNameInQueueName=false
spring.cloud.stream.solace.bindings.articleError-out-0.producer.content-type=application/json
spring.cloud.stream.binders.solace.type=solace
spring.cloud.stream.binders.solace.environment.solace.java.host=tcps://
spring.cloud.stream.binders.solace.environment.solace.java.msgVpn=
spring.cloud.stream.binders.solace.environment.solace.java.clientUsername=
spring.cloud.stream.binders.solace.environment.solace.java.clientPassword=
spring.cloud.stream.binders.solace.environment.solace.java.connectRetries=-1
spring.cloud.stream.binders.solace.environment.solace.java.reconnectRetries=-1
spring.cloud.stream.binders.solace.environment.solace.java.max-redelivery=-1
spring.cloud.stream.binders.solace.environment.solace.java.concurrency=-1
spring.cloud.stream.binders.solace.environment.solace.java.maxAttempts=-1
spring.cloud.stream.binders.solace.environment.solace.java.apiProperties.ssl_validate_certificate=true
spring.cloud.stream.binders.solace.environment.solace.java.apiProperties.ssl_trust_store_password=
spring.cloud.stream.binders.solace.environment.solace.java.apiProperties.ssl_trust_store=src/main/resources/solace-certificates.jks
endpoints.health.sensitive=false
management.security.enabled=false
management.health.jms.enabled=false
management.endpoint.metrics.enabled=true
management.endpoint.prometheus.enabled=true
management.endpoint.health.show-details=always
management.endpoint.health.show-components=always
management.endpoints.web.exposure.include=*
management.metrics.export.prometheus.enabled=true

Hi @glenn_esl ,
We are actually working on adding spring boot actuator support for the Solace binder right now. Keep your eyes peeled as it should be available in our next Spring Cloud Stream Binder release (expected next month). The previous info that I posted in this thread was just items that were exposed by the framework itself.
It will not be exactly what you see in this PR , but it will end up looking similar to what you see there. Actually, go ahead and watch that PR for updates as it will either be merged w/ changes or closed in favor of another PR.

@marc.dipasquale when this health future will be available?

The next Spring Cloud Stream Binder release is targeted for the first half of March so coming very soon :slight_smile: