Springboot actuator health component check puts whole service as down? - spring-boot

we have multiple health checks for our application, but if one of the components health check fails - then the status of the whole application changes to "DOWN".
What we want is the application status to still be "UP", even if a single component fails.
How to prevent a single failing component check to change the application status?
Here is an example of our health checks:
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": { "database": "MariaDB", "validationQuery": "isValid()" }
},
"discoveryComposite": {
"description": "Discovery Client not initialized",
"status": "UNKNOWN",
"components": {
"discoveryClient": {
"description": "Discovery Client not initialized",
"status": "UNKNOWN"
}
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 117404147712,
"free": 102471593984,
"threshold": 10485760,
"exists": true
}
},
"linkFlow": {
"status": "UP",
"details": { "message": "LinkFLow er tilgjengelig" }
},
"nissy": {
"status": "UP",
"details": { "message": "Nissy er tilgjengelig" }
},
"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" },
"zisson": {
"status": "UP",
"details": { "message": "Zisson API er tilgjengelig" }
}
}
}
Implementation of healthcheck:
#Component
#Slf4j
public class ZissonHealthIndicator implements HealthIndicator {
private final ZissonAPIGateway zissonAPIGateway;
public ZissonHealthIndicator(ZissonAPIGateway zissonAPIGateway) {
this.zissonAPIGateway = zissonAPIGateway;
}
#Override
public Health health() {
if (zissonAPIGateway.isAvailable()) {
return Health.up().withDetail("message", "Zisson API er tilgjengelig").build();
}
log.warn("Helsesjekk: Zisson er utilgjengelig");
return Health.down().withDetail("message", "Zisson er ikke tilgjengelig").build();
}
}

Yes you can. Just create your own StatusAggregator:
#Component
public class CustomHealthAggregator extends SimpleStatusAggregator {
#Override
public Status getAggregateStatus(Set<Status> statuses) {
// or some othe logic here
if (statuses.contains(Status.UP)) {
return Status.UP;
}
return super.getAggregateStatus(statuses);
}
}

Related

Why does my Spring Boot Actuator tell me the application state is DOWN in K8S, but UP outside

I have a Spring boot application with actuator enabled so that I can use the /actuator/health URIs as probes. My application talks to redis, JMS and cosmos so those are checked as part of the health response.
When I run the application at my command prompt I get the following JSON response, with a HTTP 200 code indicating all is well.
{
"status": "UP",
"components": {
"cosmos": {
"status": "UP",...
},
"diskSpace": {
"status": "UP",...
},
"grpcChannel": {
"status": "UP",...
},
"jms": {
"status": "UP",...
}
},
"ping": {
"status": "UP"
},
"redis": {
"status": "UP",...
}
}
}
When I deploy the application to a K8S environment (Rancher locally or Azure in the cloud) the application knows it is now in a K8S environment and so adds some additional stuff to the health check. However while the main components are all UP - as for the local run - I now get a 503 Service Unavailable response code and the livenessState is DOWN and the readinessState is OUT_OF_SERVICE.
{
"status": "DOWN",
"components": {
"cosmos": {
"status": "UP",...
},
"diskSpace": {
"status": "UP",...
},
"grpcChannel": {
"status": "UP",...
},
"jms": {
"status": "UP",...
}
},
"livenessState": {
"status": "DOWN"
},
"ping": {
"status": "UP"
},
"readinessState": {
"status": "OUT_OF_SERVICE"
},
"redis": {
"status": "UP",
"details": {
"cluster_size": 3,
"slots_up": 16384,
"slots_fail": 0
}
}
},
"groups": [
"liveness",
"readiness"
]
}
What additional checks is the actuator doing when in K8S and what do I need to do for actuator to report it is all OK? Any suggestions as to how to troubleshoot the problem?
I have searched for more information on SO and the wider web, but cannot find anything that helps find out what the K8S checks are specifically looking for.
Thank you.

Spring Boot: how to modify health check response data format

My application enables health check with application properties like below.
# health check config
management.endpoint.health.show-details=always
management.endpoints.web.base-path=/
management.endpoints.web.path-mapping.health=health
So if i request GET /health to application.
i get this reponse.
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "H2",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 247862915072,
"free": 139563880448,
"threshold": 10485760,
"path": "C:\\Users\\~~",
"exists": true
}
},
"ping": {
"status": "UP"
}
}
}
When i saw this response. i dont wanna response some data like database type (H2).
I know that i can't change response data from actutator.
But is there way to modify data like below code in spring boot?
#RestController
#RequestMapping("/health")
public class HealthController {
#GetMapping()
public HealthResponse get() {
// Below code is just example. i wonder that i can get origin /health api data.
HealthOriginResponse origin = HealthModule.getData()
return new HealthResponse(origin.status, origin.db.status, origin.diskSpace.status);
}
}
If there is no way. i consider to use interceptor for modifying.

NEAR transaction without receipt but with receipt_outcome

When querying archival node for transactions with EXPERIMENTAL_tx_status method, some transactions have no receipts while having receipts_outcome. How is that possible, and how is that transaction different from others?
If I understand correctly, receipts_outcome are the results of applying receipts. According to explorer, this transaction has Convert Transaction To Receipt part, so there should be some receipts generated.
According to documentation
A Receipt is the only actionable object in the system. When we talk about "processing a transaction" on the NEAR platform, this eventually means "applying receipts" at some point.
A good mental model is to think of a Receipt as a paid message to be executed at the destination (receiver). And a Transaction is an externally issued request to create the Receipt (there is a 1 to 1 relationship).
My query
{
"jsonrpc": "2.0",
"id": "2",
"method": "EXPERIMENTAL_tx_status",
"params": ["7beNxrbHxMRspJWT9NeEVwx719kVcmY9tRdPG9SYro26", "bumbleee99.near"]
}
Response
{
"jsonrpc": "2.0",
"result": {
"status": {
"SuccessValue": ""
},
"transaction": {
"signer_id": "bumbleee99.near",
"public_key": "ed25519:DFM5GRGbpNkk4XkhcFnRUFeKG8a3nzTH8NwZp754pC48",
"nonce": 59080995000003,
"receiver_id": "bumbleee99.near",
"actions": [
{
"AddKey": {
"public_key": "ed25519:CUoNs153GHrPZ9F8HpvhzFr1mwuUFUdGQsRNE2CTNjVH",
"access_key": {
"nonce": 0,
"permission": "FullAccess"
}
}
}
],
"signature": "ed25519:15v34qoyCHSvSL5uLcaPqD9vXvjcPrCaZVStCMms8e58C62z2UHiazwUXzHajPEgdHpwn7s4J9dd5UPmtvzbYgM",
"hash": "7beNxrbHxMRspJWT9NeEVwx719kVcmY9tRdPG9SYro26"
},
"transaction_outcome": {
"proof": [
{
"hash": "ECKDm5FVhzit7Wqs9sEyBB9NtuTrVRZmWwcxkkg2yUh4",
"direction": "Right"
},
{
"hash": "E4VXdwsNj3fZCbP6y9YH3M5oZHPDcdArqU9kbZJa95Qp",
"direction": "Right"
}
],
"block_hash": "ASY6HgDUQUXUa99L7dPEfghKEnEk5SNkwQrx24u3Fobz",
"id": "7beNxrbHxMRspJWT9NeEVwx719kVcmY9tRdPG9SYro26",
"outcome": {
"logs": [],
"receipt_ids": [
"JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ"
],
"gas_burnt": 209824625000,
"tokens_burnt": "20982462500000000000",
"executor_id": "bumbleee99.near",
"status": {
"SuccessReceiptId": "JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ"
},
"metadata": {
"version": 1,
"gas_profile": null
}
}
},
"receipts_outcome": [
{
"proof": [
{
"hash": "8RwCWE9HgqenPKv8JW9eg2iSLMaQW82wvebYSfjPbdTY",
"direction": "Left"
},
{
"hash": "E4VXdwsNj3fZCbP6y9YH3M5oZHPDcdArqU9kbZJa95Qp",
"direction": "Right"
}
],
"block_hash": "ASY6HgDUQUXUa99L7dPEfghKEnEk5SNkwQrx24u3Fobz",
"id": "JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ",
"outcome": {
"logs": [],
"receipt_ids": [],
"gas_burnt": 209824625000,
"tokens_burnt": "20982462500000000000",
"executor_id": "bumbleee99.near",
"status": {
"SuccessValue": ""
},
"metadata": {
"version": 1,
"gas_profile": []
}
}
}
],
"receipts": []
},
"id": "2"
}
You could see that both transaction_outcome.outcome.receipt_ids and transaction_outcome.outcome.status are pointing to a receipt with ID JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ. I've tried querying node about this receipt with EXPERIMENTAL_receipt method like this
{
"jsonrpc": "2.0",
"id": "2",
"method": "EXPERIMENTAL_receipt",
"params": {"receipt_id": "JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ"}
}
yet the node returns error indicating, that there is no receipt with given ID
{
"jsonrpc": "2.0",
"error": {
"name": "HANDLER_ERROR",
"cause": {
"name": "UNKNOWN_RECEIPT",
"info": {
"receipt_id": "JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ"
}
},
"code": -32000,
"message": "Server error",
"data": {
"name": "UNKNOWN_RECEIPT",
"info": {
"receipt_id": "JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ"
}
}
},
"id": "2"
}
TL;DR the receipt is a local receipt
The transaction from your example is a simple AddKey action where the sender is the receiver (remember this, it's important)
"Execute" transaction (means to convert the transaction into a Receipt)
Apply the Receipts
As the result of the conversion of the transaction into a receipt is your transaction_outcome
"outcome": {
"receipt_ids": [
"JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ"
],
"status": {
"SuccessReceiptId": "JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ"
},
This receipt is about to be applied and the predecessor_id and the receiver_id are equal. In nearcore such receipts are called local receipts (sir - sender-is-receiver) and those receipts are not stored in the nearcore database.
We emulate them on NEAR Indexer Framework side (that's why you can see Receipt JDnBrxh6L9KFgVUEg6U8d39rEUEmbvLQ5tZQUmJTMyFJ on the transaction details page on NEAR Explorer)
And because nearcore doesn't store such receipts in the database you got UNKNOWN_RECEIPT from the RPC.

health details no longer show up in SBA UI after SpringBoot upgrade

I upgraded my apps from SpringBoot 2.1.18 to SpringBoot 2.5.7 and I see that health details such as diskSpace no longer show up in SpringBootAdmin UI after the upgrade. I am using SBA 2.0.0 and have not changed that.
The health endpoint still includes them, but the top level key has been renamed from details to components. I assumed that was what was confusing SBA, but even stranger my custom health indicator still displays in SBA despite this.
Any idea how I can solve this?
old
{
"status": "OUT_OF_SERVICE",
"details": {
"myCustomHealthIndicator": {
"status": "OUT_OF_SERVICE",
"details": {
"Reason": "Suspect State message received",
"StateText": "Connection failed; Channel disconnected"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 107361579008,
"free": 94436716544,
"threshold": 10485760
}
},
"refreshScope": {
"status": "UP"
}
}
}
new
{
"status": "OUT_OF_SERVICE",
"components": {
"diskSpace": {
"status": "UP",
"details": {
"total": 107361579008,
"free": 100102447104,
"threshold": 10485760,
"exists": true
}
},
"livenessState": {
"status": "UP"
},
"ping": {
"status": "UP"
},
"readinessState": {
"status": "UP"
},
"myCustomHealthIndicator": {
"status": "OUT_OF_SERVICE",
"details": {
"Reason": "Suspect State message received",
"StateText": "Connection failed; Channel disconnected"
}
}
},
"groups": [
"liveness",
"readiness"
]
}
Solved by upgrading to Spring Boot Admin Server 2.5.4 ( de.codecentric:spring-boot-admin-starter-server:jar:2.5.4 )

How can I output the return value of a Method like totalAmount in an Spring Rest Entity

is it possible to output a return Value totalAmount of an Entity ShoppingCart that is not a Value in the Class but a Method? So for example I have a Class Shoppingcart with a List of Items. and a Method totalAmount. Now when I make a request to the API with the URL http://localhost:8082/carts/1 I want to get a response like the following:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2
}
],
"totalAmount": "1051,50",
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Currently the response of an API request looks like the following:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2
}
],
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Is there an Annotation that do this job or something other. I tried to add it in the CartResourceProcessor (org.springframework.hateoas.ResourceProcessor) but there is only the possibility to add additional links. Or do I need to add a Class value totalAmount?
Yes you can achieve that by annotating your method with Jackson #JsonProperty annotation
Code sample
#JsonProperty("totalAmount")
public double computeTotalAmount()
{
// compute totalAmout and return it
}
And to answer the possible next question you get after reading this. How the totalAmount is calculated. Here the snippet
public Class Cart{
// some Class values
#JsonProperty("totalAmount")
public BigDecimal total(){
return items.stream()
.map(Item::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
public class Item{
// some Item values
#JsonProperty("totalAmount")
public BigDecimal total(){
return price.multiply(new BigDecimal(this.quantity));
}
}
Outputs something similar to this:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3,
"totalAmount": 901.5
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2,
"totalAmount": 150
}
],
"totalAmount": 1051.5,
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Hope it helps you :)

Resources