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.
handleMessageneeds to be in an external class, that you inject and then call thehandleMessage. As loing as you keep calling it as an internal method it will simply not work.handleErrorneeds to be in a separate class (sorry wrong method). The annotations for the metrics are on that, if you don't put that in an external class with the annotations on it it will not be passing through the proxy.