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