Ether Framework
Unified API docs for Ether modules
Loading...
Searching...
No Matches
EnhancedHealthHandler.java
Go to the documentation of this file.
1package dev.rafex.ether.http.jetty12.health;
2
3/*-
4 * #%L
5 * ether-http-jetty12
6 * %%
7 * Copyright (C) 2025 - 2026 Raúl Eduardo González Argote
8 * %%
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 * #L%
27 */
28
29import java.lang.management.ManagementFactory;
30import java.lang.management.MemoryMXBean;
31import java.lang.management.OperatingSystemMXBean;
32import java.time.Instant;
33import java.util.LinkedHashMap;
34import java.util.List;
35import java.util.Map;
36import java.util.Set;
37
38import javax.sql.DataSource;
39
40import dev.rafex.ether.http.core.Route;
41import dev.rafex.ether.http.jetty12.exchange.JettyHttpExchange;
42import dev.rafex.ether.http.jetty12.handler.NonBlockingResourceHandler;
43import dev.rafex.ether.http.jetty12.response.JettyApiResponses;
44import dev.rafex.ether.json.JsonCodec;
45import dev.rafex.ether.json.JsonUtils;
46import dev.rafex.ether.observability.core.probe.ProbeAggregator;
47import dev.rafex.ether.observability.core.probe.ProbeCheck;
48import dev.rafex.ether.observability.core.probe.ProbeKind;
49import dev.rafex.ether.observability.core.probe.ProbeResult;
50import dev.rafex.ether.observability.core.probe.ProbeStatus;
51
53
54 private static final JsonCodec JSON_CODEC = JsonUtils.codec();
55 private static final JettyApiResponses RESPONSES = new JettyApiResponses(JSON_CODEC);
56
57 private final List<ProbeCheck> probes;
58
59 public EnhancedHealthHandler(final DataSource dataSource) {
60 super(JSON_CODEC);
61 this.probes = createProbes(dataSource);
62 }
63
64 @Override
65 protected String basePath() {
66 return "/health";
67 }
68
69 @Override
70 protected List<Route> routes() {
71 return List.of(Route.of("/", Set.of("GET")));
72 }
73
74 @Override
75 public boolean get(final dev.rafex.ether.http.core.HttpExchange x) {
76 final var jx = asJetty(x);
77
78 final var report = ProbeAggregator.aggregate(ProbeKind.HEALTH, probes);
79 final var overallStatus = report.status();
80
81 final var checksMap = new LinkedHashMap<String, Object>();
82 for (final var result : report.results()) {
83 checksMap.put(result.name(), Map.of("status", result.status().name(), "detail",
84 result.detail() != null ? result.detail() : "", "kind", result.kind().name()));
85 }
86
87 final var body = new LinkedHashMap<String, Object>();
88
89 body.put("service", "kiwi");
90 body.put("version", "0.1.0-SNAPSHOT");
91 body.put("timestamp", Instant.now().toString());
92
93 body.put("status", overallStatus.name());
94 body.put("kind", ProbeKind.HEALTH.name());
95
96 body.put("system", getSystemMetrics());
97
98 if (!checksMap.isEmpty()) {
99 body.put("checks", checksMap);
100 }
101
102 final int httpStatus = overallStatus == ProbeStatus.DOWN ? 503 : 200;
103 RESPONSES.json(jx.response(), jx.callback(), httpStatus, body);
104 return true;
105 }
106
107 @Override
108 public Set<String> supportedMethods() {
109 return Set.of("GET");
110 }
111
112 private List<ProbeCheck> createProbes(final DataSource dataSource) {
113 return List.of(dbProbe(dataSource), memoryProbe(), cpuProbe(), diskProbe(), applicationProbe());
114 }
115
116 private static ProbeCheck dbProbe(final DataSource ds) {
117 return () -> {
118 try (var conn = ds.getConnection()) {
119 final var ok = conn.isValid(1);
120 return new ProbeResult("database", ProbeKind.READINESS, ok ? ProbeStatus.UP : ProbeStatus.DOWN,
121 ok ? "connected" : "validation failed");
122 } catch (final Exception e) {
123 return new ProbeResult("database", ProbeKind.READINESS, ProbeStatus.DOWN, e.getMessage());
124 }
125 };
126 }
127
128 private static ProbeCheck memoryProbe() {
129 return () -> {
130 final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
131 final var heapUsage = memoryBean.getHeapMemoryUsage();
132 final var maxHeap = heapUsage.getMax();
133 final var usedHeap = heapUsage.getUsed();
134
135 final double usagePercentage = maxHeap > 0 ? ((double) usedHeap / maxHeap) * 100 : 0;
136
137 final var status = usagePercentage > 90
138 ? ProbeStatus.DOWN
139 : usagePercentage > 80 ? ProbeStatus.DEGRADED : ProbeStatus.UP;
140
141 return new ProbeResult("memory", ProbeKind.HEALTH, status, String.format("Heap: %.1f%% used (%,d/%,d MB)",
142 usagePercentage, usedHeap / (1024 * 1024), maxHeap / (1024 * 1024)));
143 };
144 }
145
146 private static ProbeCheck cpuProbe() {
147 return () -> {
148 final OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
149 if (osBean instanceof com.sun.management.OperatingSystemMXBean sunOsBean) {
150 final double systemLoad = sunOsBean.getSystemCpuLoad();
151 final double processLoad = sunOsBean.getProcessCpuLoad();
152
153 final var status = systemLoad > 0.9 ? ProbeStatus.DEGRADED : ProbeStatus.UP;
154
155 return new ProbeResult("cpu", ProbeKind.HEALTH, status,
156 String.format("System: %.1f%%, Process: %.1f%%", systemLoad * 100, processLoad * 100));
157 }
158 return new ProbeResult("cpu", ProbeKind.HEALTH, ProbeStatus.UP, "CPU metrics not available");
159 };
160 }
161
162 private static ProbeCheck diskProbe() {
163 return () -> {
164 try {
165 final var file = new java.io.File(".");
166 final var total = file.getTotalSpace();
167 final var free = file.getFreeSpace();
168
169 final double freePercentage = total > 0 ? ((double) free / total) * 100 : 0;
170
171 final var status = freePercentage < 10
172 ? ProbeStatus.DOWN
173 : freePercentage < 20 ? ProbeStatus.DEGRADED : ProbeStatus.UP;
174
175 return new ProbeResult("disk", ProbeKind.HEALTH, status, String.format("Free: %.1f%% (%,d/%,d MB)",
176 freePercentage, free / (1024 * 1024), total / (1024 * 1024)));
177 } catch (final Exception e) {
178 return new ProbeResult("disk", ProbeKind.HEALTH, ProbeStatus.DOWN, e.getMessage());
179 }
180 };
181 }
182
183 private static ProbeCheck applicationProbe() {
184 return () -> {
185 final var threadCount = Thread.activeCount();
186 final var uptime = ManagementFactory.getRuntimeMXBean().getUptime();
187
188 return new ProbeResult("application", ProbeKind.HEALTH, ProbeStatus.UP,
189 String.format("Threads: %d, Uptime: %d minutes", threadCount, uptime / (60 * 1000)));
190 };
191 }
192
193 private Map<String, Object> getSystemMetrics() {
194 final var metrics = new LinkedHashMap<String, Object>();
195
196 try {
197 final var runtime = Runtime.getRuntime();
198 final var memoryBean = ManagementFactory.getMemoryMXBean();
199 final var heapUsage = memoryBean.getHeapMemoryUsage();
200 final var nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
201 final var osBean = ManagementFactory.getOperatingSystemMXBean();
202 final var runtimeBean = ManagementFactory.getRuntimeMXBean();
203
204 metrics.put("jvm", Map.of("name", runtimeBean.getVmName(), "version", runtimeBean.getVmVersion(), "vendor",
205 runtimeBean.getVmVendor(), "uptime", runtimeBean.getUptime()));
206
207 metrics.put("memory", Map.of("heap", Map
208 .of("used", heapUsage.getUsed(), "committed", heapUsage.getCommitted(), "max", heapUsage.getMax()),
209 "nonHeap",
210 Map.of("used", nonHeapUsage.getUsed(), "committed", nonHeapUsage.getCommitted(), "max",
211 nonHeapUsage.getMax()),
212 "availableProcessors", runtime.availableProcessors(), "totalMemory", runtime.totalMemory(),
213 "freeMemory", runtime.freeMemory(), "maxMemory", runtime.maxMemory()));
214
215 metrics.put("os",
216 Map.of("name", osBean.getName(), "version", osBean.getVersion(), "architecture", osBean.getArch(),
217 "availableProcessors", osBean.getAvailableProcessors(), "systemLoadAverage",
218 osBean.getSystemLoadAverage()));
219
220 } catch (final Exception e) {
221 metrics.put("error", "Failed to collect system metrics: " + e.getMessage());
222 }
223
224 return metrics;
225 }
226
227 private static JettyHttpExchange asJetty(final dev.rafex.ether.http.core.HttpExchange x) {
228 return (JettyHttpExchange) x;
229 }
230}
Utilidad para agregar resultados de múltiples probes de verificación.
static ProbeReport aggregate(final ProbeKind kind, final List< ProbeCheck > checks)
Agrega los resultados de múltiples probes de verificación.
Tipos de probes de verificación disponibles.
HEALTH
Verifica si el servicio está respondiendo (health check).
DOWN
El servicio no está disponible o no responde.
record ProbeResult(String name, ProbeKind kind, ProbeStatus status, String detail)
Resultado individual de una verificación de probe.