Skip to content

Commit 2ed618d

Browse files
feat(health,version): add health and version endpoints (#136)
* feat(health,version): add health and version endpoints * feat(health,version): add health and version endpoints without auth * fix(health): remove unused private methods * fix(health): fix exception issue * fix(health): redact error details for unauthenticated health checks * fix code quality issues and reduce cognitive complexity * feat(health): add MySQL health endpoint * refactor(health): simplify MySQL health check and remove sensitive details * fix(health): remove unused imports and variables * refactor(health): address nitpicks (configurable ES scheme, log noise, graceful shutdown, record) * fix(health): scope PROCESSLIST lock-wait check to application DB user * refactor(health): remove unused params and reuse response/error constants * fix(health): remove unused imports and methods * chore(health): clean up unused imports, params, and dead helpers * fix(health): avoid sharing JDBC connections across threads in advanced MySQL checks * refactor(health): reuse REDIS_COMPONENT constant and extract nested try block * fix(health): avoid blocking DB I/O under write lock and restore interrupt flag * fix(health): cancel in-flight futures on generic failure * feat(health,version): add index existance, read-only detection, canary write for elasticsearch health check * refactor(health): reduce cognitive complexity, remove dead throws, and clean code smells
1 parent 7c91575 commit 2ed618d

5 files changed

Lines changed: 1009 additions & 48 deletions

File tree

pom.xml

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@
245245
<artifactId>h2</artifactId>
246246
<scope>runtime</scope>
247247
</dependency>
248+
<!-- Elasticsearch client for health check -->
249+
<dependency>
250+
<groupId>org.elasticsearch.client</groupId>
251+
<artifactId>elasticsearch-rest-client</artifactId>
252+
<version>8.10.0</version>
253+
</dependency>
248254
<!--END NEW DEPENDENCIES ADDED-->
249255
</dependencies>
250256
<profiles>
@@ -286,31 +292,29 @@
286292
<version>3.3.0</version>
287293
</plugin>
288294
<plugin>
289-
<groupId>pl.project13.maven</groupId>
290-
<artifactId>git-commit-id-plugin</artifactId>
291-
<version>4.9.10</version>
295+
<groupId>io.github.git-commit-id</groupId>
296+
<artifactId>git-commit-id-maven-plugin</artifactId>
297+
<version>9.0.2</version>
292298
<executions>
293299
<execution>
294300
<id>get-the-git-infos</id>
295301
<goals>
296302
<goal>revision</goal>
297303
</goals>
304+
<phase>initialize</phase>
298305
</execution>
299306
</executions>
300307
<configuration>
301-
<dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
302-
<prefix>git</prefix>
303-
<verbose>false</verbose>
304308
<generateGitPropertiesFile>true</generateGitPropertiesFile>
305-
<generateGitPropertiesFilename>
306-
${project.build.outputDirectory}/git.properties
307-
</generateGitPropertiesFilename>
308-
<format>json</format>
309-
<gitDescribe>
310-
<skip>false</skip>
311-
<always>false</always>
312-
<dirty>-dirty</dirty>
313-
</gitDescribe>
309+
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
310+
<includeOnlyProperties>
311+
<property>^git.branch$</property>
312+
<property>^git.commit.id.abbrev$</property>
313+
<property>^git.build.version$</property>
314+
<property>^git.build.time$</property>
315+
</includeOnlyProperties>
316+
<failOnNoGitDirectory>false</failOnNoGitDirectory>
317+
<failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo>
314318
</configuration>
315319
</plugin>
316320

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* AMRIT – Accessible Medical Records via Integrated Technology
3+
* Integrated EHR (Electronic Health Records) Solution
4+
*
5+
* Copyright (C) "Piramal Swasthya Management and Research Institute"
6+
*
7+
* This file is part of AMRIT.
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program. If not, see https://www.gnu.org/licenses/.
21+
*/
22+
23+
package com.iemr.common.identity.controller.health;
24+
25+
import java.time.Instant;
26+
import java.util.Map;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import org.springframework.http.HttpStatus;
30+
import org.springframework.http.ResponseEntity;
31+
import org.springframework.web.bind.annotation.GetMapping;
32+
import org.springframework.web.bind.annotation.RequestMapping;
33+
import org.springframework.web.bind.annotation.RestController;
34+
import jakarta.servlet.http.Cookie;
35+
import jakarta.servlet.http.HttpServletRequest;
36+
import com.iemr.common.identity.service.health.HealthService;
37+
import com.iemr.common.identity.utils.JwtAuthenticationUtil;
38+
import io.swagger.v3.oas.annotations.Operation;
39+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
40+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
41+
import io.swagger.v3.oas.annotations.tags.Tag;
42+
43+
44+
@RestController
45+
@RequestMapping("/health")
46+
@Tag(name = "Health Check", description = "APIs for checking infrastructure health status")
47+
public class HealthController {
48+
49+
private static final Logger logger = LoggerFactory.getLogger(HealthController.class);
50+
51+
private final HealthService healthService;
52+
private final JwtAuthenticationUtil jwtAuthenticationUtil;
53+
54+
public HealthController(HealthService healthService, JwtAuthenticationUtil jwtAuthenticationUtil) {
55+
this.healthService = healthService;
56+
this.jwtAuthenticationUtil = jwtAuthenticationUtil;
57+
}
58+
@GetMapping
59+
@Operation(summary = "Check infrastructure health",
60+
description = "Returns the health status of MySQL, Redis, Elasticsearch, and other configured services")
61+
@ApiResponses({
62+
@ApiResponse(responseCode = "200", description = "Services are UP or DEGRADED (operational with warnings)"),
63+
@ApiResponse(responseCode = "503", description = "One or more critical services are DOWN")
64+
})
65+
public ResponseEntity<Map<String, Object>> checkHealth() {
66+
logger.info("Health check endpoint called");
67+
68+
try {
69+
Map<String, Object> healthStatus = healthService.checkHealth();
70+
String overallStatus = (String) healthStatus.get("status");
71+
72+
// Return 503 only if DOWN; 200 for both UP and DEGRADED (DEGRADED = operational with warnings)
73+
HttpStatus httpStatus = "DOWN".equals(overallStatus) ? HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.OK;
74+
75+
logger.debug("Health check completed with status: {}", overallStatus);
76+
return new ResponseEntity<>(healthStatus, httpStatus);
77+
78+
} catch (Exception e) {
79+
logger.error("Unexpected error during health check", e);
80+
81+
// Return sanitized error response
82+
Map<String, Object> errorResponse = Map.of(
83+
"status", "DOWN",
84+
"timestamp", Instant.now().toString()
85+
);
86+
87+
return new ResponseEntity<>(errorResponse, HttpStatus.SERVICE_UNAVAILABLE);
88+
}
89+
}
90+
}

src/main/java/com/iemr/common/identity/controller/version/VersionController.java

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,54 +21,59 @@
2121
*/
2222
package com.iemr.common.identity.controller.version;
2323

24-
import java.io.BufferedReader;
2524
import java.io.IOException;
2625
import java.io.InputStream;
27-
import java.io.InputStreamReader;
26+
import java.util.LinkedHashMap;
27+
import java.util.Map;
28+
import java.util.Properties;
2829

2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
32+
33+
import org.springframework.http.MediaType;
34+
import org.springframework.http.ResponseEntity;
3135
import org.springframework.web.bind.annotation.GetMapping;
3236
import org.springframework.web.bind.annotation.RestController;
3337

34-
import com.iemr.common.identity.utils.response.OutputResponse;
35-
3638
import io.swagger.v3.oas.annotations.Operation;
3739

3840
@RestController
3941
public class VersionController {
4042

41-
private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
43+
private final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
4244

45+
private static final String UNKNOWN_VALUE = "unknown";
46+
4347
@Operation(summary = "Get version information")
44-
@GetMapping(value = "/version",consumes = "application/json", produces = "application/json")
45-
public String versionInformation() {
46-
OutputResponse output = new OutputResponse();
48+
@GetMapping(value = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
49+
public ResponseEntity<Map<String, String>> versionInformation() {
50+
Map<String, String> response = new LinkedHashMap<>();
4751
try {
4852
logger.info("version Controller Start");
49-
output.setResponse(readGitProperties());
50-
} catch (Exception e) {
51-
output.setError(e);
52-
}
53-
53+
Properties gitProperties = loadGitProperties();
54+
response.put("buildTimestamp", gitProperties.getProperty("git.build.time", UNKNOWN_VALUE));
55+
response.put("version", gitProperties.getProperty("git.build.version", UNKNOWN_VALUE));
56+
response.put("branch", gitProperties.getProperty("git.branch", UNKNOWN_VALUE));
57+
response.put("commitHash", gitProperties.getProperty("git.commit.id.abbrev", UNKNOWN_VALUE));
58+
} catch (Exception e) {
59+
logger.error("Failed to load version information", e);
60+
response.put("buildTimestamp", UNKNOWN_VALUE);
61+
response.put("version", UNKNOWN_VALUE);
62+
response.put("branch", UNKNOWN_VALUE);
63+
response.put("commitHash", UNKNOWN_VALUE);
64+
}
5465
logger.info("version Controller End");
55-
return output.toString();
66+
return ResponseEntity.ok(response);
5667
}
57-
private String readGitProperties() throws Exception {
58-
ClassLoader classLoader = getClass().getClassLoader();
59-
InputStream inputStream = classLoader.getResourceAsStream("git.properties");
60-
61-
return readFromInputStream(inputStream);
62-
}
63-
private String readFromInputStream(InputStream inputStream)
64-
throws IOException {
65-
StringBuilder resultStringBuilder = new StringBuilder();
66-
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
67-
String line;
68-
while ((line = br.readLine()) != null) {
69-
resultStringBuilder.append(line).append("\n");
70-
}
71-
}
72-
return resultStringBuilder.toString();
68+
69+
private Properties loadGitProperties() throws IOException {
70+
Properties properties = new Properties();
71+
try (InputStream input = getClass().getClassLoader()
72+
.getResourceAsStream("git.properties")) {
73+
if (input != null) {
74+
properties.load(input);
75+
}
76+
}
77+
return properties;
7378
}
7479
}

0 commit comments

Comments
 (0)