Suppose I have an API which calls another
API and I want to have a health check of this endpoint as well. Is this possible with Actuator or do I need to write custom code?
You can implement custom health indicator by implementing org.springframework.boot.actuate.health.HealthIndicator and adding it as bean in spring context. Something like below should get you started:
#Component
public class CustomHealthCheck implements HealthIndicator {
#Override
public Health health() {
int errorCode = check();
if (errorCode != 0) {
return Health.down()
.withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
public int check() {
// Custom logic to check health
return 0;
}
}
Related
I use Spring Boot 2.1.2.RELEASE, and I try to use Micrometer with CompositeMeterRegistry. My goal is to publish some selected meters to ElasticSearch. The code below shows my sample config. The problem is, that the filter is completely ignored (so all metrics are sent to ElasticSearch), although I can see in the logs that it was processed ("filter reply of meter ..." lines).
Strangely, if I define the MeterFilter as a Spring bean, then it's applied to ALL registries (however, I want it to be applied only on "elasticMeterRegistry").
Here is a sample configuration class:
#Configuration
public class AppConfiguration {
#Bean
public ElasticConfig elasticConfig() {
return new ElasticConfig() {
#Override
#Nullable
public String get(final String k) {
return null;
}
};
}
#Bean
public MeterRegistry meterRegistry(final ElasticConfig elasticConfig) {
final CompositeMeterRegistry registry = new CompositeMeterRegistry();
registry.add(new SimpleMeterRegistry());
registry.add(new JmxMeterRegistry(new JmxConfig() {
#Override
public Duration step() {
return Duration.ofSeconds(10);
}
#Override
#Nullable
public String get(String k) {
return null;
}
}, Clock.SYSTEM));
final ElasticMeterRegistry elasticMeterRegistry = new ElasticMeterRegistry(elasticConfig, Clock.SYSTEM);
elasticMeterRegistry.config().meterFilter(new MeterFilter() {
#Override
public MeterFilterReply accept(Meter.Id id) {
final MeterFilterReply reply =
id.getName().startsWith("logback")
? MeterFilterReply.NEUTRAL
: MeterFilterReply.DENY;
log.info("filter reply of meter {}: {}", id.getName(), reply);
return reply;
}
});
registry.add(elasticMeterRegistry);
return registry;
}
}
So, I expect ElasticSearch to receive only "logback" metrics, and JMX to receive all metrics.
UPDATE:
I have played with filters and found a "solution", but I don't really understand why the code above doesn't work.
This works:
elasticMeterRegistry.config().meterFilter(new MeterFilter() {
#Override
public MeterFilterReply accept(Meter.Id id) {
final MeterFilterReply reply =
id.getName().startsWith("logback")
? MeterFilterReply.ACCEPT
: MeterFilterReply.DENY;
log.info("filter reply of meter {}: {}", id.getName(), reply);
return reply;
}
});
The difference is: I return ACCEPT instead of NEUTRAL.
Strangely, the following code does not work (ES gets all metrics):
elasticMeterRegistry.config().meterFilter(
MeterFilter.accept(id -> id.getName().startsWith("logback")));
But this works:
elasticMeterRegistry.config().meterFilter(
MeterFilter.accept(id -> id.getName().startsWith("logback")));
elasticMeterRegistry.config().meterFilter(
MeterFilter.deny());
CONCLUSION:
So, it seems that instead of NEUTRAL, the filter should return ACCEPT. But for meters not starting with "logback", my original filter (with NEUTRAL) returns DENY. Then why are those metrics published to ElasticSearch registry?
Can someone explain this?
This is really a composite of questions. I'll just point out a few points.
For the MeterRegistry bean you defined, Spring Boot will auto-configure an ElasticMeterRegistry bean as there's no ElasticMeterRegistry bean. Instead of creating a CompositeMeterRegistry bean on your own, just define a custom ElasticMeterRegistry bean which is applied the MeterFilter you want and let Spring Boot create one (CompositeMeterRegistry bean) for you.
For MeterFilterReply, ACCEPT will accept the meter immediately, DENY will deny the meter immediately, and NEUTRAL will postpone the decision to next filter(s). Basically meters will be accepted unless there's any DENY.
Thanks for reading ahead of time. In my main method I have a PublishSubscribeChannel
#Bean(name = "feeSchedule")
public SubscribableChannel getMessageChannel() {
return new PublishSubscribeChannel();
}
In a service that does a long running process it creates a fee schedule that I inject the channel into
#Service
public class FeeScheduleCompareServiceImpl implements FeeScheduleCompareService {
#Autowired
MessageChannel outChannel;
public List<FeeScheduleUpdate> compareFeeSchedules(String oldStudyId) {
List<FeeScheduleUpdate> sortedResultList = longMethod(oldStudyId);
outChannel.send(MessageBuilder.withPayload(sortedResultList).build());
return sortedResultList;
}
}
Now this is the part I'm struggling with. I want to use completable future and get the payload of the event in the future A in another spring bean. I need future A to return the payload from the message. I think want to create a ServiceActivator to be the message end point but like I said, I need it to return the payload for future A.
#org.springframework.stereotype.Service
public class SFCCCompareServiceImpl implements SFCCCompareService {
#Autowired
private SubscribableChannel outChannel;
#Override
public List<SFCCCompareDTO> compareSFCC(String state, int service){
ArrayList<SFCCCompareDTO> returnList = new ArrayList<SFCCCompareDTO>();
CompletableFuture<List<FeeScheduleUpdate>> fa = CompletableFuture.supplyAsync( () ->
{ //block A WHAT GOES HERE?!?!
outChannel.subscribe()
}
);
CompletableFuture<List<StateFeeCodeClassification>> fb = CompletableFuture.supplyAsync( () ->
{
return this.stateFeeCodeClassificationRepository.findAll();
}
);
CompletableFuture<List<SFCCCompareDTO>> fc = fa.thenCombine(fb,(a,b) ->{
//block C
//get in this block when both A & B are complete
Object theList = b.stream().forEach(new Consumer<StateFeeCodeClassification>() {
#Override
public void accept(StateFeeCodeClassification stateFeeCodeClassification) {
a.stream().forEach(new Consumer<FeeScheduleUpdate>() {
#Override
public void accept(FeeScheduleUpdate feeScheduleUpdate) {
returnList new SFCCCompareDTO();
}
});
}
}).collect(Collectors.toList());
return theList;
});
fc.join();
return returnList;
}
}
Was thinking there would be a service activator like:
#MessageEndpoint
public class UpdatesHandler implements MessageHandler{
#ServiceActivator(requiresReply = "true")
public List<FeeScheduleUpdate> getUpdates(Message m){
return (List<FeeScheduleUpdate>) m.getPayload();
}
}
Your question isn't clear, but I'll try to help you with some info.
Spring Integration doesn't provide CompletableFuture support, but it does provide an async handling and replies.
See Asynchronous Gateway for more information. And also see Asynchronous Service Activator.
outChannel.subscribe() should come with the MessageHandler callback, by the way.
I'm trying to integrate Spring Boot Actuator with my companies existing infrastructure. To do this I need to be able to customize the status message. For instance if the app is up and running correctly I need to return a 200 and a plain text body of "HAPPY" from the health actuator endpoint.
Is such customization currently possible? Since the Status class is final I can't extend it, but I think that would work.
Spring Boot uses a HealthAggregator to aggregate all of the statuses from the individual health indicators into a single health for the entire application. You can plug in a custom aggregator that delegates to Boot's default aggregator, OrderedHealthAggregator, and then maps UP to HAPPY:
#Bean
public HealthAggregator healthAggregator() {
return new HappyHealthAggregator(new OrderedHealthAggregator());
}
static class HappyHealthAggregator implements HealthAggregator {
private final HealthAggregator delegate;
HappyHealthAggregator(HealthAggregator delegate) {
this.delegate = delegate;
}
#Override
public Health aggregate(Map<String, Health> healths) {
Health result = this.delegate.aggregate(healths);
if (result.getStatus() == Status.UP) {
return new Health.Builder(new Status("HAPPY"), result.getDetails())
.build();
}
return result;
}
}
If you want to take complete control over the format of the response, then you'll need to write your own MVC endpoint implementation. You could use the existing HealthMvcEndpointclass in Spring Boot as a super class and override its invoke method.
I have create a PostgreSQL health indicator like this:
#Component
public class PostgresHealthIndicator extends AbstractHealthIndicator {
#Autowired
DataSource postgresDataSource;
public DataSourceHealthIndicator dbHealthIndicator() {
DataSourceHealthIndicator indicator = new DataSourceHealthIndicator(postgresDataSource);
return indicator;
}
#Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
Health h = dbHealthIndicator().health();
Status status = h.getStatus();
if (status != null && "DOWN".equals(status.getCode())) {
builder.down();
} else {
builder.up();
}
}
}
In my Application.java I am scanning this package for this component:
#ComponentScan({"com.bp.health"})
In my application.properties I have the following set:
endpoints.health.sensitive=false
endpoints.health.id=health
endpoints.health.enabled=true
when I hit {url}/health I see:
{"status":"DOWN"}
what do I need to do to get the custom health indicator to show?
You need to be authenticated to see all the details.
Alternatively you could set
management.security.enabled=false
endpoints.health.sensitive=false
to see full content unauthenticated
More information about that can be found here:
Production ready monitoring
Why would you want to do all that? That code doesn't even compile and Spring Boot does check your data sources by default already. If you only see the status, that's because you're not authenticated, check this table in the doc for more details.
We have a (working) SOAP web service based on Spring WS with DefaultWsdl11Definition.
This is basically what it looks like:
#Endpoint("name")
public class OurEndpoint {
#PayloadRoot(namespace = "somenamespace", localPart = "localpart")
public void onMessage(#RequestPayload SomePojo pojo) {
// do stuff
}
}
It is wired in Spring and it is correctly processing all of our SOAP requests. The only problem is that the method returns a 202 Accepted. This is not what the caller wants, he'd rather have us return 204 No Content (or if that is not possible an empty 200 OK).
Our other endpoints have a valid response object, and do return 200 OK. It seems void causes 202 when 204 might be more appropriate?
Is it possible to change the response code in Spring WS? We can't seem to find the correct way to do this.
Things we tried and didn't work:
Changing the return type to:
HttpStatus.NO_CONTENT
org.w3c.dom.Element <- not accepted
Adding #ResponseStatus <- this is for MVC, not WS
Any ideas?
Instead of what I wrote in the comments it is possibly the easiest to create a delegation kind of solution.
public class DelegatingMessageDispatcher extends MessageDispatcher {
private final WebServiceMessageReceiver delegate;
public DelegatingMessageDispatcher(WebServiceMessageReceiver delegate) {
this.delegate = delegate;
}
public void receive(MessageContext messageContext) throws Exception {
this.delegate.receive(messageContext);
if (!messageContext.hasResponse()) {
TransportContext tc = TransportContextHolder.getTransportContext();
if (tc != null && tc.getConnection() instanceof HttpServletConnection) {
((HttpServletConnection) tc.getConnection()).getHttpServletResponse().setStatus(200);
}
}
}
}
Then you need to configure a bean named messageDispatcher which would wrap the default SoapMessageDispatcher.
#Bean
public MessageDispatcher messageDispatcher() {
return new DelegatingMessageDispatcher(soapMessageDispatcher());
}
#Bean
public MessageDispatcher soapMessageDispatcher() {
return new SoapMessageDispatcher();
}
Something like that should do the trick. Now when response is created (In the case of a void return type), the status as you want is send back to the client.
When finding a proper solutions we've encountered some ugly problems:
Creating custom adapters/interceptors is problematic because the handleResponse method isn't called by Spring when you don't have a response (void)
Manually setting the status code doesn't work because HttpServletConnection keeps a boolean statusCodeSet which doesn't get updated
But luckily we managed to get it working with the following changes:
/**
* If a web service has no response, this handler returns: 204 No Content
*/
public class NoContentInterceptor extends EndpointInterceptorAdapter {
#Override
public void afterCompletion(MessageContext messageContext, Object o, Exception e) throws Exception {
if (!messageContext.hasResponse()) {
TransportContext tc = TransportContextHolder.getTransportContext();
if (tc != null && tc.getConnection() instanceof HttpServletConnection) {
HttpServletConnection connection = ((HttpServletConnection) tc.getConnection());
// First we force the 'statusCodeSet' boolean to true:
connection.setFaultCode(null);
// Next we can set our custom status code:
connection.getHttpServletResponse().setStatus(204);
}
}
}
}
Next we need to register this interceptor, this can be easily done using Spring's XML:
<sws:interceptors>
<bean class="com.something.NoContentInterceptor"/>
</sws:interceptors>
A big thanks to #m-deinum for pointing us in the right direction!
To override the afterCompletion method really helped me out in the exact same situation. And for those who use code based Spring configuration, hereĀ“s how one can add the interceptor for a specific endpoint.
Annotate the custom interceptor with #Component, next register the custom interceptor to a WsConfigurerAdapter like this:
#EnableWs
#Configuration
public class EndpointConfig extends WsConfigurerAdapter {
/**
* Add our own interceptor for the specified WS endpoint.
* #param interceptors
*/
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new PayloadRootSmartSoapEndpointInterceptor(
new NoContentInterceptor(),
"NAMESPACE",
"LOCAL_PART"
));
}
}
NAMESPACE and LOCAL_PART should correspond to the endpoint.
If someone ever wanted to set custom HTTP status when returning non-void response, here is solution:
Spring Boot WS-Server - Custom Http Status