I am trying to design an application that will show metrics about it's health and other custom metrics, like how long each function runs and how many times etc. I am on spring boot 2.7.1. I want to create metrics only by using annotations (@Counted, @Timed), but for some reason it is not working for me. It feels like I've tried almost every solution on the internet there is.
My yml file:
logging:
level:
root: "INFO"
server:
port: 8083
spring:
redis:
host: "localhost"
port: 6100
task:
scheduling:
thread-name-prefix: "scheduling-"
pool:
size: 2
management:
endpoint:
health:
show-details: always
group:
live:
include: livenessState
ready:
include: readinessState, appReadinessIndicator
probes:
enabled: true
livenessState: true
readinessState: true
endpoints:
web:
exposure:
include: "*"
my main:
@SpringBootApplication
@ConfigurationPropertiesScan
@EnableScheduling
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
@Bean
RouterFunction<ServerResponse> routerFunction() {
return route(GET("/health"), req ->
ServerResponse.temporaryRedirect(URI.create("/actuator/health"))
.build());
}
@Bean
RouterFunction<ServerResponse> routerFunction2() {
return route(GET("/metrics"), req ->
ServerResponse.temporaryRedirect(URI.create("/actuator/prometheus"))
.build());
}
}
the class I am trying to get metrics from.
@Service
public class MyService {
@Autowired
private Template template;
.........
@Value
....
@Timed("MyTimer")
@Counted("MyCounter")
public void handleError() {
...
code
...
}
public void handleMessage (param){
if (message == null || message.getInfo() == null) {
this.HandleError();
}else if
...
}
@PostConstruct
public void setupSubscriber() {
for (Map.Entry<String, NamespaceConfiguration> namespaceConfiguration : myConfig.getNamespaces().entrySet()) {
if (!namespaceConfiguration.getKey().equals(MyConfig.NAMESPACE)) {
this.template.listenToPattern(namespaceConfiguration.getValue().getRequest())
.doOnSubscribe(c -> logger.info("Successfully subscribed to {}", namespaceConfiguration.getValue().getRequest()))
.doOnNext(msg -> logger.info(msg.getChannel() + ": Received msg " + msg.getMessage()))
.onErrorContinue(this::handleError)
.log("OUTER", Level.INFO)
.subscribe(this::handleMessage);
}
}
myReadinessIndicator.markAsReady();
}
For it to work I've tried adding this. But it didn't work either.
@Configuration
@EnableAspectJAutoProxy
public class TimedConfiguration {
@Bean
public TimedAspect timedAspect(MeterRegistry registry){
return new TimedAspect(registry);
}
}
Only solution that worked is this one. But with a lot of methods its repetitive. I've also tried moving method to separate class. Only with this solution I've found my metrics in
http://localhost:port/actuator/prometheus
and also in
http://localhost:port/actuator/metrics
@Service
@Component
public class MyService {
@Autowired
private Template template;
.........
@Value
....
public MyService (MeterRegistry registry){
Counter timeElapsedHandleError = Counter.builder("time.handleError").
description("Time in ms of run fnc handleError").
register(registry);
Counter countRunHandleError = Counter.builder("count.handleError").
description("Counts how many times fnc handleError was run").
register(registry);
...
}
public void handleError() {
long startTime = System.nanoTime();
metrics.countErrors.increment();
...
code
...
metrics.timeErrors.increment(System.nanoTime()-startTime);
}
public void handleMessage (param){
if (message == null || message.getInfo() == null) {
this.HandleError();
}
}
@PostConstruct
public void setupSubscriber() {
for (Map.Entry<String, NamespaceConfiguration> namespaceConfiguration : myConfig.getNamespaces().entrySet()) {
if (!namespaceConfiguration.getKey().equals(MyConfig.NAMESPACE)) {
this.template.listenToPattern(namespaceConfiguration.getValue().getRequest())
.doOnSubscribe(c -> logger.info("Successfully subscribed to {}", namespaceConfiguration.getValue().getRequest()))
.doOnNext(msg -> logger.info(msg.getChannel() + ": Received msg " + msg.getMessage()))
.onErrorContinue(this::handleError)
.log("OUTER", Level.INFO)
.subscribe(this::handleMessage);
}
}
myReadinessIndicator.markAsReady();
}
I've tried moving method to another class like this. Am I doing it wrong?
@Service
@Component
public class MyService {
@Autowired
private Template template;
.........
@Value
....
@Autowired
ServiceHandler serviceHandler;
@Autowire
MessageHandler messageHandler;
@PostConstruct
public void setupSubscriber() {
for (Map.Entry<String, NamespaceConfiguration> namespaceConfiguration : myConfig.getNamespaces().entrySet()) {
if (!namespaceConfiguration.getKey().equals(MyConfig.NAMESPACE)) {
this.template.listenToPattern(namespaceConfiguration.getValue().getRequest())
.doOnSubscribe(c -> logger.info("Successfully subscribed to {}", namespaceConfiguration.getValue().getRequest()))
.doOnNext(msg -> logger.info(msg.getChannel() + ": Received msg " + msg.getMessage()))
.onErrorContinue(serviceHandler::handleError)
.log("OUTER", Level.INFO)
.subscribe(messageHandler::handleMessage);
}
}
myReadinessIndicator.markAsReady();
}
I need to call multiple methods from handleMessage(). I've tried moving it to separate class.
@Component
public class MessageHandler{
@Autowire
ServiceHanler serviceHanler;
public void handleMessage (param){
if (message == null || message.getInfo() == null) {
serviceHanler.HandleError();
}else if (something){
serviceHandler.handleXYMessage();
}
}
}
and the second class:
@Component
public class ServiceHandler {
@Timed("MyTimer")
@Counted("MyCounter")
public void handleError() {
...
code
...
}
@Timed("MyTimer2")
@Counted("MyCounter2")
public void handleXYMessage(){
...
code
...
}
}
So I resolved the issue. The problem was with call of the function with the annotation. I was expecting to see metrics right away after application start up. But the metrics show up after the function was run.