0

I am somewhat new to Spring Framework. I have a web application written with Spring (4.2.1). I'm trying to expose metrics using Micrometer library and will be scraping with Prometheus.

The relevant structure of the application is this:
- core-module (JAR)
- webservice-module (WAR)

I created a PrometheusService class which is a bean defined in core-module. Defined inside the bean is the PrometheusMeterRegistry and Counter:

@Service
public class PrometheusService {

    private static PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    private static Counter newAssetCounter = Counter
            .builder("new_asset_counter")
            .description("count of created assets")
            .tags("region", "na")
            .register(registry);

    public PrometheusService() {
        new JvmMemoryMetrics().bindTo(registry);
        new DiskSpaceMetrics(new File("/")).bindTo(registry);
        new ProcessorMetrics().bindTo(registry);
        new UptimeMetrics().bindTo(registry);
    }

    public static PrometheusMeterRegistry getRegistry() {
        return registry;
    }

    public Counter getNewAssetCounter() {
        return this.newAssetCounter;
    }

}

I created MetricsResource which is an HttpServlet that exposes the /metrics endpoint. When trying to @Autowire the PrometheusService bean, it was always null here. A quick search told me that HttpServlet isn't managed by Spring. If I wanted to @Autowire, I needed to add something like this:

SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(webApplicationContext);

Now, I was able to @Autowire the PrometheusService bean within the Servlet.

The Counter defined in the bean gets incremented within the core-module. The MetricsResource doGet method writes the metrics stored in the PrometheusMeterRegistry.

@WebServlet("/metrics")
public class MetricsResource extends HttpServlet  {

    private PrometheusService promService; // @Autowired
    private PrometheusMeterRegistry registry;

    @Override
    public void init() throws ServletException {
        super.init();

//        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(webApplicationContext);

//        promService = (PrometheusService) getServletContext().getAttribute("prometheusService");

//        WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//        AutowireCapableBeanFactory ctx = context.getAutowireCapableBeanFactory();
//        ctx.autowireBean(this);
    }

    @Override
    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setContentType(TextFormat.CONTENT_TYPE_004);

        registry = promService.getRegistry();

        Writer writer = resp.getWriter();
        try {
            registry.scrape(writer);
            writer.flush();
        } finally {
            writer.close();
        }
    }
}

The problem though, is that the value of the Counter is always 0 at the /metrics endpoint.

No matter if it's @Autowired or if I'm manually trying to get the bean.

How could this be? My PrometheusService bean is a singleton. Even the PrometheusMeterRegistry and the Counter are marked static, so why am I getting a different object in my servlet? After some more searching, I found that Spring will create one singleton bean per container. So what I'm assuming is happening here is there are two containers or contexts. A main application context and a servlet context.

Some things I've tried:
Making PrometheusService implement ApplicationContextAware
Using a ServiceLocator class that implements ApplicationContextAware and returns beans
Adding context-params to web.xml
Using ServletContextAttributeExporter in app-context.xml
Using WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext())

I continue to get a new instance of the object. All I want to do is be able to create and expose custom metrics with Micrometer. Is my approach flawed? How can I access the correct bean from within my HttpServlet?

Spring dependency injection to other instance
http://senthadev.com/sharing-spring-container-between-modules-in-a-web-application.html
Spring dependency injection to other instance
ApplicationContext and ServletContext

CB61
  • 11
  • 3
  • I think, it's needed to setup ApplicationContext in a ServletContextListener. Examples of code are available in this answer: https://stackoverflow.com/questions/18745770/spring-injection-into-servlet – Daniil Feb 11 '20 at 09:24

1 Answers1

0

Try processInjectionBasedOnServletContext(Object target, ServletContext servletContext).

Adrian
  • 3,321
  • 2
  • 29
  • 46