Metrics for din Spring REST API

REST Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

I denne vejledning integrerer vi grundlæggende metrics i en Spring REST API.

Vi bygger den metriske funktionalitet ud først ved hjælp af enkle Servlet-filtre og derefter ved hjælp af en Spring Boot Actuator.

2. Den web.xml

Lad os starte med at registrere et filter - “MetricFilter" - ind i web.xml af vores app:

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

Bemærk, hvordan vi kortlægger filteret for at dække alle anmodninger, der kommer ind - “/*” - hvilket naturligvis er fuldt konfigurerbart.

3. Servletfilteret

Lad os nu oprette vores brugerdefinerede filter:

offentlig klasse MetricFilter implementerer Filter {private MetricService metricService; @ Override public void init (FilterConfig config) kaster ServletException {metricService = (MetricService) WebApplicationContextUtils .getRequiredWebApplicationContext (config.getServletContext ()) .getBean ("metricService"); } @ Override offentlig ugyldighed doFilter (ServletRequest anmodning, ServletResponse svar, FilterChain kæde) kaster java.io.IOException, ServletException {HttpServletRequest httpRequest = ((HttpServletRequest) anmodning); String req = httpRequest.getMethod () + "" + httpRequest.getRequestURI (); chain.doFilter (anmodning, svar); int status = ((HttpServletResponse) svar) .getStatus (); metricService.increaseCount (req, status); }}

Da filteret ikke er en standardbønne, injicerer vi ikke metricService men i stedet hente det manuelt - via ServletContext.

Bemærk også, at vi fortsætter udførelsen af ​​filterkæden ved at ringe til doFilter API her.

4. Metrisk - Statuskodetællinger

Næste - lad os se på vores enkle MetricService:

@Service offentlig klasse MetricService {privat ConcurrentMap statusMetric; public MetricService () {statusMetric = new ConcurrentHashMap (); } offentlig ugyldig forhøjelseCount (strenganmodning, int-status) {Integer statusCount = statusMetric.get (status); hvis (statusCount == null) {statusMetric.put (status, 1); } andet {statusMetric.put (status, statusCount + 1); }} offentligt kort getStatusMetric () {return statusMetric; }}

Vi bruger en i hukommelsen ConcurrentMap at holde optællingerne for hver type HTTP-statuskode.

Nu - for at få vist denne grundlæggende metric - kortlægger vi den til en Controller metode:

@RequestMapping (værdi = "/ status-metrisk", metode = RequestMethod.GET) @ResponseBody offentligt kort getStatusMetric () {return metricService.getStatusMetric (); }

Og her er et eksempel på svar:

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

5. Metrisk - statuskoder efter anmodning

Næste - lad os registrere metrics for tællinger efter anmodning:

@Service offentlig klasse MetricService {privat ConcurrentMap metricMap; public void increaseCount (String request, int status) {ConcurrentHashMap statusMap = metricMap.get (anmodning); hvis (statusMap == null) {statusMap = ny ConcurrentHashMap (); } Heltalstælling = statusMap.get (status); hvis (count == null) {count = 1; } andet {count ++; } statusMap.put (status, count); metricMap.put (anmodning, statusMap); } offentligt kort getFullMetric () {return metricMap; }}

Vi viser metriske resultater via API:

@RequestMapping (værdi = "/ metrisk", metode = RequestMethod.GET) @ResponseBody offentligt kort getMetric () {return metricService.getFullMetric (); }

Sådan ser disse metrics ud:

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

Ifølge ovenstående eksempel havde API'en følgende aktivitet:

  • "7" anmodninger til "FÅ / brugere
  • “6” af dem resulterede i “200” statuskodesvar og kun én i en “409”

6. Metrisk - tidsseriedata

Generelle optællinger er noget nyttige i en applikation, men hvis systemet har kørt i betydelig tid - det er svært at fortælle, hvad disse målinger faktisk betyder.

Du har brug for tidssammenhæng for at dataene skal give mening og let fortolkes.

Lad os nu oprette en simpel tidsbaseret metrik; vi registrerer antallet af statuskoder pr. minut - som følger:

@Service offentlig klasse MetricService {privat ConcurrentMap timeMap; privat statisk SimpleDateFormat dateFormat = ny SimpleDateFormat ("åååå-MM-dd HH: mm"); offentlig ugyldig forhøjelseCount (strenganmodning, int-status) {String time = dateFormat.format (new Date ()); ConcurrentHashMap statusMap = timeMap.get (tid); hvis (statusMap == null) {statusMap = ny ConcurrentHashMap (); } Heltalstælling = statusMap.get (status); hvis (count == null) {count = 1; } andet {count ++; } statusMap.put (status, count); timeMap.put (time, statusMap); }}

Og getGraphData ():

offentligt objekt [] [] getGraphData () {int colCount = statusMetric.keySet (). størrelse () + 1; Indstil allStatus = statusMetric.keySet (); int rowCount = timeMap.keySet (). størrelse () + 1; Objekt [] [] resultat = nyt objekt [rækkeCount] [colCount]; resultat [0] [0] = "Tid"; int j = 1; for (int status: allStatus) {resultat [0] [j] = status; j ++; } int i = 1; ConcurrentMap tempMap; for (indgang entry: timeMap.entrySet ()) {result [i] [0] = entry.getKey (); tempMap = entry.getValue (); for (j = 1; j <colCount; j ++) {resultat [i] [j] = tempMap.get (resultat [0] [j]); hvis (resultat [i] [j] == null) {resultat [i] [j] = 0; }} i ++; } returnere resultat }

Vi kortlægger dette nu til API:

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

Og endelig - vi skal gengive det ved hjælp af Google Charts:

  Metrisk graf google.load ("visualisering", "1", {pakker: ["corechart"]}); funktion drawChart () {$ .get ("/ metric-graph-data", funktion (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, valgmuligheder);}); } 

7. Brug af Spring Boot 1.x aktuator

I de næste par sektioner vil vi tilslutte os aktuatorfunktionaliteten i Spring Boot for at præsentere vores metrics.

Først - vi bliver nødt til at tilføje aktuatorafhængigheden til vores pom.xml:

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

7.1. Det MetricFilter

Næste - vi kan vende MetricFilter - ind i en egentlig Spring Bean:

@Komponent offentlig klasse MetricFilter implementerer filter {@Autowired private MetricService metricService; @Override offentlig ugyldighed doFilter (ServletRequest anmodning, ServletResponse svar, FilterChain kæde) kaster java.io.IOException, ServletException {chain.doFilter (anmodning, svar); int status = (((HttpServletResponse) svar) .getStatus (); metricService.increaseCount (status); }}

Dette er selvfølgelig en mindre forenkling - men en der er værd at gøre for at slippe af med den tidligere manuelle ledningsføring af afhængigheder.

7.2. Ved brug af CounterService

Lad os nu bruge CounterService at tælle forekomster for hver statuskode:

@Service offentlig klasse MetricService {@Autowired privat CounterService-tæller; privat Liste statusListe; public void increaseCount (int status) {counter.increment ("status." + status); hvis (! statusList.contains ("counter.status." + status)) {statusList.add ("counter.status." + status); }}}

7.3. Eksporter målinger ved hjælp af Metrisk depot

Dernæst - vi skal eksportere metrics - ved hjælp af Metrisk depot:

@Service offentlig klasse MetricService {@Autowired privat MetricRepository repo; privat liste statusMetrisk; privat Liste statusListe; @Scheduled (fixedDelay = 60000) private ugyldige eksportMetrics () {metrisk metric; ArrayList statusCount = ny ArrayList (); for (String status: statusList) {metric = repo.findOne (status); hvis (metrisk! = null) {statusCount.add (metric.getValue (). intValue ()); repo.reset (status); } andet {statusCount.add (0); }} statusMetric.add (statusCount); }}

Bemærk, at vi gemmer optællinger af statuskoder pr. minut.

7.4. Spring Boot PublicMetrics

Vi kan også bruge Spring Boot PublicMetrics at eksportere metrics i stedet for at bruge vores egne filtre - som følger:

Først har vi vores planlagte opgave til eksportmålinger pr. minut:

@Autowired private MetricReaderPublicMetrics publicMetrics; privat liste statusMetricsByMinute; privat Liste statusListe; privat statisk endelig SimpleDateFormat dateFormat = ny SimpleDateFormat ("åååå-MM-dd HH: mm"); @Scheduled (fixedDelay = 60000) private ugyldige eksportMetrics () {ArrayList lastMinuteStatuses = initializeStatuses (statusList.size ()); for (Metric counterMetric: publicMetrics.metrics ()) {updateMetrics (counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add (lastMinuteStatuses); }

Vi skal selvfølgelig initialisere listen over HTTP-statuskoder:

private ArrayList initializeStatuses (int størrelse) {ArrayList counterList = ny ArrayList (); for (int i = 0; i <størrelse; i ++) {counterList.add (0); } returner counterList; }

Og så opdaterer vi faktisk metrics med antal statuskoder:

privat ugyldig opdateringMetrics (Metric counterMetric, ArrayList statusCount) {String status = ""; int-indeks = -1; int oldCount = 0; hvis (counterMetric.getName (). indeholder ("counter.status.")) {status = counterMetric.getName (). substring (15, 18); // eksempel 404, 200 appendStatusIfNotExist (status, statusCount); index = statusList.indexOf (status); oldCount = statusCount.get (index) == null? 0: statusCount.get (indeks); statusCount.set (index, counterMetric.getValue (). intValue () + oldCount); }} privat ugyldigt appendStatusIfNotExist (String status, ArrayList statusCount) {if (! statusList.contains (status)) {statusList.add (status); statusCount.add (0); }}

Noter det:

  • PublicMetics status tæller navn start med “counter.status" for eksempel "counter.status.200.root
  • Vi registrerer statusantal pr. Minut på vores liste statusMetricsByMinute

Vi kan eksportere vores indsamlede data for at tegne dem i en graf - som følger:

offentligt objekt [] [] getGraphData () {Dato aktuel = ny dato (); int colCount = statusList.size () + 1; int rowCount = statusMetricsByMinute.size () + 1; Objekt [] [] resultat = nyt objekt [rækkeCount] [colCount]; resultat [0] [0] = "Tid"; int j = 1; for (String status: statusList) {result [0] [j] = status; j ++; } for (int i = 1; i <rækkeCount; i ++) {resultat [i] [0] = dateFormat.format (ny dato (current.getTime () - (60000 * (rowCount - i)))); } Liste minutOfStatuses; Liste sidst = ny ArrayList (); for (int i = 1; i <rowCount; i ++) {minuteOfStatuses = statusMetricsByMinute.get (i - 1); for (j = 1; j = j? last.get (j - 1): 0); } mens (j <colCount) {resultat [i] [j] = 0; j ++; } sidste = minutOfStatuses; } returnere resultat }

7.5. Tegn graf ved hjælp af målinger

Endelig - lad os repræsentere disse metrics via et 2-dimensioneringsarray - så vi derefter kan tegne dem:

offentligt objekt [] [] getGraphData () {Dato aktuel = ny dato (); int colCount = statusList.size () + 1; int rowCount = statusMetric.size () + 1; Objekt [] [] resultat = nyt objekt [rækkeCount] [colCount]; resultat [0] [0] = "Tid"; 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); resultat [i] [0] = dateFormat.format (ny dato (current.getTime () - (60000 * (rowCount - i)))); for (j = 1; j <= temp.størrelse (); j ++) {resultat [i] [j] = temp.get (j - 1); } mens (j <colCount) {resultat [i] [j] = 0; j ++; }} returner resultat; }

Og her er vores controller-metode getMetricData ():

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

Og her er et eksempel på svar:

[["Tid", "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. Brug af Spring Boot 2.x aktuator

I Spring Boot 2 oplevede Spring Actuators API'er en større ændring. Forårs egne målinger er blevet erstattet med Mikrometer. Så lad os skrive det samme metricseksempel ovenfor med Mikrometer.

8.1. Udskiftning CounterService Med MeterRegistry

Da vores Spring Boot-applikation allerede afhænger af aktuatorstarteren, er Micrometer allerede konfigureret automatisk. Vi kan injicere MeterRegistry i stedet for CounterService. Vi kan bruge forskellige typer Måler for at registrere målinger. Det Tæller er et af målerne:

@Autowired privat MeterRegistry registreringsdatabase; privat Liste statusListe; @Override offentligt ugyldigt aukningstal (endelig int-status) {String counterName = "counter.status." + status registry.counter (counterName). increment (1); hvis (! statusList.contains (counterName)) {statusList.add (counterName); }}

8.2. Eksport af tæller ved hjælp af MeterRegistry

I Micrometer kan vi eksportere Tæller værdier ved hjælp af MeterRegistry:

@Scheduled (fixedDelay = 60000) private void exportMetrics () {ArrayList statusCount = new ArrayList (); for (String status: statusList) {Søg søgning = registry.find (status); if (search! = null) {Counter counter = search.counter (); statusCount.add (counter! = null? ((int) counter.count ()): 0); registry.remove (tæller); } andet {statusCount.add (0); }} statusMetricsByMinute.add (statusCount); }

8.3. Udgivelsesmålinger ved hjælp af Meter

Nu kan vi også udgive metrics ved hjælp af MeterRegistry's Meter:

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

9. Konklusion

I denne artikel undersøgte vi et par enkle måder til at opbygge nogle grundlæggende metrics-muligheder i en Spring-webapplikation.

Bemærk, at tællerne er ikke trådsikker - så de er måske ikke nøjagtige uden at bruge noget som atomnumre. Dette var bevidst, bare fordi deltaet skulle være lille, og 100% nøjagtighed ikke er målet - snarere er det tidligt at se tendenser.

Der er selvfølgelig mere modne måder at registrere HTTP-metrics i en applikation på, men dette er en enkel, letvægts og supernyttig måde at gøre det på uden den ekstra kompleksitet af et fuldt udviklet værktøj.

Den fulde implementering af denne artikel kan findes i GitHub-projektet.

REST bunden

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

$config[zx-auto] not found$config[zx-overlay] not found