Metrik untuk Spring REST API Anda

REST Top

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya

1. Ikhtisar

Dalam tutorial ini, kami akan mengintegrasikan Metrik dasar ke dalam Spring REST API .

Kami akan membuat fungsionalitas metrik terlebih dahulu menggunakan Filter Servlet sederhana, kemudian menggunakan Aktuator Spring Boot.

2. web.xml

Mari kita mulai dengan mendaftarkan filter - “ MetricFilter ” - ke dalam web.xml aplikasi kita:

 metricFilter org.baeldung.web.metric.MetricFilter   metricFilter /* 

Perhatikan bagaimana kami memetakan filter untuk mencakup semua permintaan yang masuk - "/ *" - yang tentu saja dapat dikonfigurasi sepenuhnya.

3. Filter Servlet

Sekarang - mari buat filter kustom kita:

public class MetricFilter implements Filter { private MetricService metricService; @Override public void init(FilterConfig config) throws ServletException { metricService = (MetricService) WebApplicationContextUtils .getRequiredWebApplicationContext(config.getServletContext()) .getBean("metricService"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException { HttpServletRequest httpRequest = ((HttpServletRequest) request); String req = httpRequest.getMethod() + " " + httpRequest.getRequestURI(); chain.doFilter(request, response); int status = ((HttpServletResponse) response).getStatus(); metricService.increaseCount(req, status); } }

Karena filter bukan kacang standar, kami tidak akan memasukkan metricService tetapi mengambilnya secara manual - melalui ServletContext .

Perhatikan juga bahwa kami melanjutkan eksekusi rantai filter dengan memanggil API doFilter di sini.

4. Metrik - Hitungan Kode Status

Selanjutnya - mari kita lihat MetricService sederhana kami :

@Service public class MetricService { private ConcurrentMap statusMetric; public MetricService() { statusMetric = new ConcurrentHashMap(); } public void increaseCount(String request, int status) { Integer statusCount = statusMetric.get(status); if (statusCount == null) { statusMetric.put(status, 1); } else { statusMetric.put(status, statusCount + 1); } } public Map getStatusMetric() { return statusMetric; } }

Kami menggunakan ConcurrentMap dalam memori untuk menahan hitungan setiap jenis kode status HTTP.

Sekarang - untuk menampilkan metrik dasar ini - kita akan memetakannya ke metode Controller :

@RequestMapping(value = "/status-metric", method = RequestMethod.GET) @ResponseBody public Map getStatusMetric() { return metricService.getStatusMetric(); }

Dan berikut ini contoh tanggapannya:

{ "404":1, "200":6, "409":1 }

5. Metrik - Kode Status berdasarkan Permintaan

Berikutnya - mari catat metrik untuk Hitungan berdasarkan Permintaan :

@Service public class MetricService { private ConcurrentMap
    
      metricMap; public void increaseCount(String request, int status) { ConcurrentHashMap statusMap = metricMap.get(request); if (statusMap == null) { statusMap = new ConcurrentHashMap(); } Integer count = statusMap.get(status); if (count == null) { count = 1; } else { count++; } statusMap.put(status, count); metricMap.put(request, statusMap); } public Map getFullMetric() { return metricMap; } }
    

Kami akan menampilkan hasil metrik melalui API:

@RequestMapping(value = "/metric", method = RequestMethod.GET) @ResponseBody public Map getMetric() { return metricService.getFullMetric(); }

Berikut tampilan metrik ini:

{ "GET /users": { "200":6, "409":1 }, "GET /users/1": { "404":1 } }

Menurut contoh di atas, API memiliki aktivitas berikut:

  • "7" permintaan untuk "GET / pengguna "
  • "6" di antaranya menghasilkan respons kode status "200" dan hanya satu dalam "409"

6. Metrik - Data Rangkaian Waktu

Penghitungan keseluruhan agak berguna dalam aplikasi, tetapi jika sistem telah berjalan untuk waktu yang lama - sulit untuk mengetahui arti sebenarnya dari metrik ini .

Anda membutuhkan konteks waktu agar data masuk akal dan mudah diinterpretasikan.

Sekarang mari kita buat metrik berbasis waktu sederhana; kami akan mencatat jumlah kode status per menit - sebagai berikut:

@Service public class MetricService{ private ConcurrentMap
    
      timeMap; private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); public void increaseCount(String request, int status) { String time = dateFormat.format(new Date()); ConcurrentHashMap statusMap = timeMap.get(time); if (statusMap == null) { statusMap = new ConcurrentHashMap(); } Integer count = statusMap.get(status); if (count == null) { count = 1; } else { count++; } statusMap.put(status, count); timeMap.put(time, statusMap); } }
    

Dan getGraphData () :

public Object[][] getGraphData() { int colCount = statusMetric.keySet().size() + 1; Set allStatus = statusMetric.keySet(); int rowCount = timeMap.keySet().size() + 1; Object[][] result = new Object[rowCount][colCount]; result[0][0] = "Time"; int j = 1; for (int status : allStatus) { result[0][j] = status; j++; } int i = 1; ConcurrentMap tempMap; for (Entry
    
      entry : timeMap.entrySet()) { result[i][0] = entry.getKey(); tempMap = entry.getValue(); for (j = 1; j < colCount; j++) { result[i][j] = tempMap.get(result[0][j]); if (result[i][j] == null) { result[i][j] = 0; } } i++; } return result; }
    

Kami sekarang akan memetakan ini ke API:

@RequestMapping(value = "/metric-graph-data", method = RequestMethod.GET) @ResponseBody public Object[][] getMetricData() { return metricService.getGraphData(); }

Dan akhirnya - kami akan menampilkannya menggunakan Google Charts:

  Metric Graph    google.load("visualization", "1", {packages : [ "corechart" ]}); function drawChart() { $.get("/metric-graph-data",function(mydata) { var data = google.visualization.arrayToDataTable(mydata); var options = {title : 'Website Metric', hAxis : {title : 'Time',titleTextStyle : {color : '#333'}}, vAxis : {minValue : 0}}; var chart = new google.visualization.AreaChart(document.getElementById('chart_div')); chart.draw(data, options); }); } 

7. Menggunakan Spring Boot 1.x Actuator

Di beberapa bagian berikutnya, kita akan menghubungkan ke fungsionalitas Aktuator di Spring Boot untuk menyajikan metrik kita.

Pertama - kita perlu menambahkan ketergantungan aktuator ke pom.xml kita :

 org.springframework.boot spring-boot-starter-actuator 

7.1. The MetricFilter

Selanjutnya - kita dapat mengubah MetricFilter - menjadi kacang Musim Semi yang sebenarnya:

@Component public class MetricFilter implements Filter { @Autowired private MetricService metricService; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException { chain.doFilter(request, response); int status = ((HttpServletResponse) response).getStatus(); metricService.increaseCount(status); } }

Ini, tentu saja, adalah penyederhanaan kecil - tetapi yang layak dilakukan untuk menyingkirkan kabel dependensi yang sebelumnya manual.

7.2. Menggunakan CounterService

Mari sekarang gunakan CounterService untuk menghitung kejadian untuk setiap Kode Status:

@Service public class MetricService { @Autowired private CounterService counter; private List statusList; public void increaseCount(int status) { counter.increment("status." + status); if (!statusList.contains("counter.status." + status)) { statusList.add("counter.status." + status); } } }

7.3. Ekspor Metrik Menggunakan MetricRepository

Selanjutnya - kita perlu mengekspor metrik - menggunakan MetricRepository :

@Service public class MetricService { @Autowired private MetricRepository repo; private List
    
      statusMetric; private List statusList; @Scheduled(fixedDelay = 60000) private void exportMetrics() { Metric metric; ArrayList statusCount = new ArrayList(); for (String status : statusList) { metric = repo.findOne(status); if (metric != null) { statusCount.add(metric.getValue().intValue()); repo.reset(status); } else { statusCount.add(0); } } statusMetric.add(statusCount); } }
    

Perhatikan bahwa kami menyimpan jumlah kode status per menit .

7.4. Spring Boot PublicMetrics

Kita juga dapat menggunakan Spring Boot PublicMetrics untuk mengekspor metrik daripada menggunakan filter kita sendiri - sebagai berikut:

Pertama, kami memiliki tugas terjadwal untuk mengekspor metrik per menit :

@Autowired private MetricReaderPublicMetrics publicMetrics; private List
    
      statusMetricsByMinute; private List statusList; private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); @Scheduled(fixedDelay = 60000) private void exportMetrics() { ArrayList lastMinuteStatuses = initializeStatuses(statusList.size()); for (Metric counterMetric : publicMetrics.metrics()) { updateMetrics(counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add(lastMinuteStatuses); }
    

Kami, tentu saja, perlu menginisialisasi daftar kode status HTTP:

private ArrayList initializeStatuses(int size) { ArrayList counterList = new ArrayList(); for (int i = 0; i < size; i++) { counterList.add(0); } return counterList; }

Dan kemudian kami akan benar-benar memperbarui metrik dengan jumlah kode status :

private void updateMetrics(Metric counterMetric, ArrayList statusCount) { String status = ""; int index = -1; int oldCount = 0; if (counterMetric.getName().contains("counter.status.")) { status = counterMetric.getName().substring(15, 18); // example 404, 200 appendStatusIfNotExist(status, statusCount); index = statusList.indexOf(status); oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index); statusCount.set(index, counterMetric.getValue().intValue() + oldCount); } } private void appendStatusIfNotExist(String status, ArrayList statusCount) { if (!statusList.contains(status)) { statusList.add(status); statusCount.add(0); } }

Perhatikan bahwa:

  • PublicMetics status counter name start with “counter.status” for example “counter.status.200.root
  • We keep record of status count per minute in our list statusMetricsByMinute

We can export our collected data to draw it in a graph – as follows:

public Object[][] getGraphData() { Date current = new Date(); int colCount = statusList.size() + 1; int rowCount = statusMetricsByMinute.size() + 1; Object[][] result = new Object[rowCount][colCount]; result[0][0] = "Time"; int j = 1; for (String status : statusList) { result[0][j] = status; j++; } for (int i = 1; i < rowCount; i++) { result[i][0] = dateFormat.format( new Date(current.getTime() - (60000 * (rowCount - i)))); } List minuteOfStatuses; List last = new ArrayList(); for (int i = 1; i < rowCount; i++) { minuteOfStatuses = statusMetricsByMinute.get(i - 1); for (j = 1; j = j ? last.get(j - 1) : 0); } while (j < colCount) { result[i][j] = 0; j++; } last = minuteOfStatuses; } return result; }

7.5. Draw Graph Using Metrics

Finally – let's represent these metrics via a 2 dimension array – so that we can then graph them:

public Object[][] getGraphData() { Date current = new Date(); int colCount = statusList.size() + 1; int rowCount = statusMetric.size() + 1; Object[][] result = new Object[rowCount][colCount]; result[0][0] = "Time"; int j = 1; for (String status : statusList) { result[0][j] = status; j++; } ArrayList temp; for (int i = 1; i < rowCount; i++) { temp = statusMetric.get(i - 1); result[i][0] = dateFormat.format (new Date(current.getTime() - (60000 * (rowCount - i)))); for (j = 1; j <= temp.size(); j++) { result[i][j] = temp.get(j - 1); } while (j < colCount) { result[i][j] = 0; j++; } } return result; }

And here is our Controller method getMetricData():

@RequestMapping(value = "/metric-graph-data", method = RequestMethod.GET) @ResponseBody public Object[][] getMetricData() { return metricService.getGraphData(); }

And here is a sample response:

[ ["Time","counter.status.302","counter.status.200","counter.status.304"], ["2015-03-26 19:59",3,12,7], ["2015-03-26 20:00",0,4,1] ]

8. Using Spring Boot 2.x Actuator

In Spring Boot 2, Spring Actuator's APIs witnessed a major change. Spring's own metrics have been replaced with Micrometer. So let's write the same metrics example above with Micrometer.

8.1. Replacing CounterService With MeterRegistry

As our Spring Boot application already depends on the Actuator starter, Micrometer is already auto-configured. We can inject MeterRegistry instead of CounterService. We can use different types of Meter to capture metrics. The Counter is one of the Meters:

@Autowired private MeterRegistry registry; private List statusList; @Override public void increaseCount(final int status) { String counterName = "counter.status." + status; registry.counter(counterName).increment(1); if (!statusList.contains(counterName)) { statusList.add(counterName); } }

8.2. Exporting Counts Using MeterRegistry

In Micrometer, we can export the Counter values using MeterRegistry:

@Scheduled(fixedDelay = 60000) private void exportMetrics() { ArrayList statusCount = new ArrayList(); for (String status : statusList) { Search search = registry.find(status); if (search != null) { Counter counter = search.counter(); statusCount.add(counter != null ? ((int) counter.count()) : 0); registry.remove(counter); } else { statusCount.add(0); } } statusMetricsByMinute.add(statusCount); }

8.3. Publishing Metrics Using Meters

Now we can also publish Metrics using MeterRegistry's Meters:

@Scheduled(fixedDelay = 60000) private void exportMetrics() { ArrayList lastMinuteStatuses = initializeStatuses(statusList.size()); for (Meter counterMetric : publicMetrics.getMeters()) { updateMetrics(counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add(lastMinuteStatuses); } private void updateMetrics(final Meter counterMetric, final ArrayList statusCount) { String status = ""; int index = -1; int oldCount = 0; if (counterMetric.getId().getName().contains("counter.status.")) { status = counterMetric.getId().getName().substring(15, 18); // example 404, 200 appendStatusIfNotExist(status, statusCount); index = statusList.indexOf(status); oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index); statusCount.set(index, (int)((Counter) counterMetric).count() + oldCount); } }

9. Conclusion

In this article, we explored a few simple ways to build out some basic metrics capabilities into a Spring web application.

Note that the counters aren't thread-safe – so they might not be exact without using something like atomic numbers. This was deliberate just because the delta should be small and 100% accuracy isn't the goal – rather, spotting trends early is.

Tentu saja ada cara yang lebih matang untuk merekam metrik HTTP dalam sebuah aplikasi, tetapi ini adalah cara yang sederhana, ringan, dan sangat berguna untuk melakukannya tanpa kerumitan ekstra dari alat yang lengkap.

Implementasi lengkap artikel ini dapat ditemukan di proyek GitHub.

REST bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya