diff --git a/src/main/java/com/restcountries/log/MetricsLogger.java b/src/main/java/com/restcountries/log/MetricsLogger.java new file mode 100644 index 0000000..5c914c8 --- /dev/null +++ b/src/main/java/com/restcountries/log/MetricsLogger.java @@ -0,0 +1,113 @@ +package com.restcountries.log; + +import io.micronaut.scheduling.annotation.Scheduled; +import jakarta.inject.Singleton; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.atomic.AtomicLong; + +@Singleton +public class MetricsLogger { + + private final RequestCounter counter; + private final AtomicLong globalCounter = new AtomicLong(0); + + private final AtomicLong dailyCounter = new AtomicLong(0); + private LocalDate currentDay = LocalDate.now(); + + public MetricsLogger(RequestCounter counter) { + this.counter = counter; + } + + @Scheduled(fixedRate = "6h") + void logStats() { + long requests = counter.getTotalRequests(); + + if (requests == 0) { + return; + } + + long totalRequests = globalCounter.addAndGet(requests); + dailyCounter.addAndGet(requests); + + int windowMinutes = 360; + double avgRps = requests / (windowMinutes * 60.0); + + LocalDateTime now = LocalDateTime.now(); + + writeLine( + now, + windowMinutes, + requests, + totalRequests, + avgRps, + "" + ); + + counter.reset(); + + checkDailySummary(now); + } + + private void checkDailySummary(LocalDateTime now) { + LocalDate today = now.toLocalDate(); + + if (!today.equals(currentDay)) { + writeDailySummary(); + dailyCounter.set(0); + currentDay = today; + } + } + + private void writeDailySummary() { + long dailyTotal = dailyCounter.get(); + + double avgRps = dailyTotal / 86400.0; // 24h + + LocalDateTime endOfDay = currentDay.atTime(23, 59, 59); + + writeLine( + endOfDay, + 1440, + dailyTotal, + null, + avgRps, + "DAILY_SUMMARY" + ); + } + + private void writeLine( + LocalDateTime timestamp, + int windowMinutes, + Long requests, + Long totalRequests, + double avgRps, + String notes + ) { + String line = String.join(",", + timestamp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), + String.valueOf(windowMinutes), + String.valueOf(requests), + totalRequests != null ? String.valueOf(totalRequests) : "", + String.format("%.2f", avgRps), + notes + ) + "\n"; + + File file = new File("/tmp/request-stats.csv"); + boolean writeHeader = !file.exists(); + + try (FileWriter fw = new FileWriter(file, true)) { + if (writeHeader) { + fw.write("timestamp,window_minutes,requests,total_requests,avg_rps,notes\n"); + } + fw.write(line); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/restcountries/log/RequestCounter.java b/src/main/java/com/restcountries/log/RequestCounter.java new file mode 100644 index 0000000..d22f8fe --- /dev/null +++ b/src/main/java/com/restcountries/log/RequestCounter.java @@ -0,0 +1,43 @@ +package com.restcountries.log; + +import jakarta.inject.Singleton; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +@Singleton +public class RequestCounter { + + private final AtomicLong totalRequests = new AtomicLong(0); + private final ConcurrentHashMap endpointCounts = new ConcurrentHashMap<>(); + + public void increment(String path) { + totalRequests.incrementAndGet(); + endpointCounts + .computeIfAbsent(path, k -> new AtomicLong(0)) + .incrementAndGet(); + } + + public long getTotalRequests() { + return totalRequests.get(); + } + + public ConcurrentHashMap getEndpointCounts() { + return endpointCounts; + } + + public List> getTopEndpoints() { + return endpointCounts.entrySet() + .stream() + .map(e -> Map.entry(e.getKey(), e.getValue().get())) + .sorted((a, b) -> Long.compare(b.getValue(), a.getValue())) // DESC + .toList(); + } + + public void reset() { + totalRequests.set(0); + endpointCounts.clear(); + } +} diff --git a/src/main/java/com/restcountries/log/RequestLoggingFilter.java b/src/main/java/com/restcountries/log/RequestLoggingFilter.java new file mode 100644 index 0000000..5d9f24a --- /dev/null +++ b/src/main/java/com/restcountries/log/RequestLoggingFilter.java @@ -0,0 +1,22 @@ +package com.restcountries.log; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.MutableHttpResponse; +import io.micronaut.http.annotation.Filter; +import io.micronaut.http.filter.HttpServerFilter; +import io.micronaut.http.filter.ServerFilterChain; +import jakarta.inject.Inject; +import org.reactivestreams.Publisher; + +@Filter("/**") +public class RequestLoggingFilter implements HttpServerFilter { + + @Inject + RequestCounter counter; + + @Override + public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { + counter.increment(request.getPath()); + return chain.proceed(request); + } +} \ No newline at end of file