0

Using Spring Boot 2.5.7, Micrometer 1.7.6, Ehcache 3.9.7, and have spring-boot-starter-actuator on the classpath. I'm building a batch application (not a web app, no exposed actuator APIs). I have an ehcache.xml configured to enable statistics:

    <eh:service>
        <jsr107:defaults enable-management="true" enable-statistics="true"/>
    </eh:service>

    <eh:cache alias="myCache" uses-template="default">
      <eh:key-type>java.lang.String</eh:key-type>
      <eh:value-type>java.lang.String</eh:value-type>
      <eh:listeners>
        <eh:listener>
          <eh:class>com.company.package.MyListener</eh:class>
          <!-- more event config ... -->
        </eh:listener>
      </eh:listeners>
    </eh:cache>

I would like to code the listener to periodically write cache statistics to a log. Pseudocode would look something like:

    @Autowired
    CacheManager mgr;  

    mgr.getCacheNames().forEach( cacheName -> {
        writeCacheStats(cacheName);
    });

    void writeCacheStats(String cacheName) {
        // get statistics for cacheName from Micrometer or Boot... HOW?
        // write statistics to log
    }

I have combed through Spring Boot Actuator and Micrometer docs plus several blog posts and cannot figure out how to do this. Most of the docs seem to assume one is monitoring via APIs, which is not what I need. It seems like a basic use case so I suspect I'm missing something obvious. I'm hoping someone can point me in the right direction.

user944849
  • 14,524
  • 2
  • 61
  • 83

1 Answers1

0

If you have spring-boot-starter-cache also in the classpath, you have access to the metrics defined by micrometer in JCacheMetrics and CacheMeterBinder classes. You can access them by injecting a MeterRegistry bean. Code to log the metrics could look like below:

import java.util.Collections;
import java.util.Map;

import org.springframework.stereotype.Component;

import io.micrometer.core.instrument.FunctionCounter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@RequiredArgsConstructor
public class CacheUtil {
    private final MeterRegistry meterRegistry;

    public void logCacheMetrics() {
        LOGGER.info("Cache metrics: size={}, hits={}, misses={}, puts={}, evictions={}, removals={}",
                getGaugeValue("cache.size"),
                getFunctionCounterValue("cache.gets", Collections.singletonMap("result", "hit")),
                getFunctionCounterValue("cache.gets", Collections.singletonMap("result", "miss")),
                getFunctionCounterValue("cache.puts"),
                getFunctionCounterValue("cache.evictions"),
                getGaugeValue("cache.removals"));
    }

    private Meter getMeter(String meterName, Map<String, String> tags) {
        return meterRegistry.getMeters().stream()
                .filter(meter -> meter.getId().getName().equals(meterName) &&
                        (tags.isEmpty()
                                || tags.entrySet().stream()
                                        .allMatch(inputTag -> meter.getId().getTags().stream()
                                                .anyMatch(meterTag -> meterTag.getKey().equals(inputTag.getKey())
                                                        && meterTag.getValue().equals(inputTag.getValue())))))
                .findFirst()
                .orElse(null);
    }

    private double getGaugeValue(String gaugeName) {
        Gauge gauge = (Gauge) getMeter(gaugeName, Collections.emptyMap());

        return gauge != null ? gauge.value() : 0;
    }

    private double getFunctionCounterValue(String counterName, Map<String, String> tags) {
        FunctionCounter counter = (FunctionCounter) getMeter(counterName, tags);

        return counter != null ? counter.count() : 0;
    }

    private double getFunctionCounterValue(String counterName) {
        return getFunctionCounterValue(counterName, Collections.emptyMap());
    }
}

This produces logs that look something like below:

06:34:53.821 [http-nio-8080-exec-6] INFO  i.g.d.u.CacheUtil - Cache metrics: size=0.0, hits=12.0, misses=2.0, puts=2.0, evictions=0.0, removals=0.0
06:34:54.257 [http-nio-8080-exec-7] INFO  i.g.d.u.CacheUtil - Cache metrics: size=0.0, hits=13.0, misses=2.0, puts=2.0, evictions=0.0, removals=0.0

Note that as these metrics are common to different cache implementations, some metrics(like size and evictions in this case) might show as 0 as they might not have been recorded for Ehcache. Sample application that logs these metrics at runtime can be found on github

devatherock
  • 2,423
  • 1
  • 8
  • 23