Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Timed not working despite registering TimedAspect explicitly - spring boot 2.1

I need to measure method-metrics using micrometer @Timed annotation. As it doesn't work on arbitrary methods; i added the configuration of @TimedAspect explicitly in my spring config. Have referred to this post for exact config Note: have tried adding a separate config class JUST for this, as well as including the TimedAspect bean as part of my existing configuration bean

How to measure service methods using spring boot 2 and micrometer

Yet, it unfortunately doesn't work. The Bean is registred and the invocation from config class goes thru successfully on startup. Found this while debugging. However, the code in the @Around never seems to execute. No error is thrown; and im able to view the default 'system' metrics on the /metrics and /prometheus endpoint.

Note: This is AFTER getting the 'method' to be invoked several times by executing a business flow. I'm aware that it probably doesn't show up in the metrics if the method isn't invoked at all

Versions: spring-boot 2.1.1, spring 5.3, micrometer 1.1.4, actuator 2.1

Tried everything going by the below posts:

How to measure service methods using spring boot 2 and micrometer

https://github.com/izeye/sample-micrometer-spring-boot/tree/timed-annotation

https://github.com/micrometer-metrics/micrometer/issues/361

Update: So, the issue seems to be ONLY when the Timed is on an abstract method, which is called via another method. Was able to reproduce it via a simple example. Refer to the @Timed("say_hello_example") annotation. It simply gets ignored and doesnt show up when i hit the prometheus endpoint.

Code: Abstract Class

public abstract class AbstractUtil {
    public abstract void sayhello();

    public void sayhellowithtimed(String passedVar) {
        System.out.println("Passed var =>"+passedVar);
        System.out.println("Calling abstract sayhello....");
        sayhello();

    }
}

Impl Class

@Component
@Scope("prototype")
public class ExampleUtil extends AbstractUtil  {

    public static final String HELLO = "HELLO";

    @Timed("dirwatcher_handler")
    public void handleDirectoryWatcherChange(WatchEvent event){
        System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context());
    }

    @Timed("say_hello_example")
    @Override
    public void sayhello() {
        System.out.println(HELLO);
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

A simple DirWatcher implementation class...

package com.example;
            
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
            
import java.io.IOException;
import java.nio.file.*;
            
@Component
@Scope("prototype")
public class StartDirWatcher implements ApplicationListener<ApplicationStartedEvent> {
            
    @Value("${directory.path:/apps}")
    public String directoryPath;
            
    @Autowired
    private ExampleUtil util;
            
    private void monitorDirectoryForChanges() throws IOException, InterruptedException {
        WatchService watchService = FileSystems.getDefault().newWatchService();
        Path path = Paths.get(directoryPath);
        path.register(
            watchService,
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_DELETE,
            StandardWatchEventKinds.ENTRY_MODIFY);
        WatchKey key;
        while ((key = watchService.take()) != null) {
            for (WatchEvent<?> event : key.pollEvents()) {
                util.handleDirectoryWatcherChange(event);
                util.sayhellowithtimed("GOD_OF_SMALL_THINGS_onAPPEvent");
            }
            key.reset();
        }
    }
            
    @Override
    public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
        try {
            monitorDirectoryForChanges();
        } catch (Throwable e) {
            System.err.println("ERROR!! "+e.getMessage());
            e.printStackTrace();
        }
    }
}

The Spring Boot Application Class

package com.example;

import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@EnableAspectJAutoProxy
@ComponentScan
@Configuration
@SpringBootApplication
public class ExampleStarter{

    @Bean
    MeterRegistryCustomizer<PrometheusMeterRegistry> metricsCommonTags() {
        return registry -> registry.config().commonTags("app.name", "example.app");
    }
    @Bean
    TimedAspect timedAspect(MeterRegistry reg) {
        return new TimedAspect(reg);
    }

    public static void main(String[] args) {
        SpringApplication.run(ExampleStarter.class, args);
    }

}

The main pom.xml file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.metrics.timed.example</groupId>
<artifactId>example-app</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.1.2</version>
</dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
</dependencies>
like image 688
Rahul Avatar asked May 30 '19 11:05

Rahul


2 Answers

I use spring boot 2.2.6.RELEASE and this MetricConfig works for me

@Configuration
public class MetricConfig {

  @Bean
  MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
    return registry -> registry.config().commonTags("application", "my app");
  }

  @Bean
  TimedAspect timedAspect(MeterRegistry registry) {
    return new TimedAspect(registry);
  }

}

In application.yml

management:
  endpoints:
    web:
      exposure:
        include: ["health", "prometheus"]
  endpoint:
    beans:
      cache:
        time-to-live: 10s
like image 144
makson Avatar answered Nov 12 '22 07:11

makson


@Timed use AOP(Aspect oriented programming) concept, in which proxy doesn't pass on to the second level of the method. you can define the second level of method in new bean/class. this way @Timed will work for second level of method call.

like image 1
Rahul Baghaniya Avatar answered Nov 12 '22 08:11

Rahul Baghaniya