From 9e32502ee3fa2a3194edafe9ae12011848aaee16 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Thu, 27 Aug 2026 16:47:15 +0900 Subject: [PATCH] =?UTF-8?q?WebLogic=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EC=95=88=EC=A0=95=EC=84=B1=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=95=ED=99=94:=20-=20jboss-logging/Gu?= =?UTF-8?q?ava=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=A1=9C=EB=93=9C=EC=8B=9C?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=EC=A0=90=EA=B2=80=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20ReadinessController=EC=97=90=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EB=A1=9C=EC=A7=81=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apim/portal/apps/ReadinessController.java | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java b/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java index 230d425..13b47e2 100644 --- a/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java +++ b/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java @@ -5,6 +5,7 @@ import java.sql.SQLException; import javax.sql.DataSource; +import org.jboss.logging.Logger; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -12,19 +13,28 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.thymeleaf.spring5.SpringTemplateEngine; +import com.google.common.util.concurrent.Futures; + /** * Readiness probe. * *

Distinct from {@link HealthCheckController} (liveness — servlet alive?). * This endpoint validates both EMS and Gateway datasources via JDBC - * {@code Connection.isValid(timeout)}, and the Thymeleaf {@link SpringTemplateEngine} - * configuration, to confirm the app is ready to serve real page requests. + * {@code Connection.isValid(timeout)}, plus three libraries that WebLogic's + * oracle_common bundles at a different version from this app (see weblogic.xml + * {@code prefer-application-packages}): Thymeleaf's {@link SpringTemplateEngine} + * (jackson), jboss-logging, and Guava. * - *

The template-engine check exists because this controller bypasses view - * resolution entirely (plain {@code @RestController} JSON) — a broken - * {@code TemplateEngine.getConfiguration()} (e.g. classpath split causing - * {@link java.util.ServiceConfigurationError} during dialect/module discovery) - * previously left every real page returning 500 while this probe still reported 200. + *

These exist because this controller bypasses view resolution entirely + * (plain {@code @RestController} JSON) — a classpath split between the app's + * copy and WebLogic's bundled copy of a preferred package can throw + * {@link LinkageError}/{@link java.util.ServiceConfigurationError} only on the + * real code paths that touch it, while this probe kept reporting 200. That + * already happened once for jackson (Thymeleaf's dialect/module discovery + * failed on every real page while /health/ready stayed green) — jboss-logging + * and Guava carry the same risk (confirmed version mismatch against + * oracle_common, not yet observed failing in production) so they get the same + * kind of forced-touch check here. * *

HTTP 200 + JSON when all checks pass. *

HTTP 503 + JSON when any check fails — body still includes the per-component @@ -50,16 +60,21 @@ public class ReadinessController { @GetMapping(value = "/health/ready", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity ready() { - String ems = check(portalDataSource); - String gw = check(gatewayDataSource); - String view = checkTemplateEngine(); - boolean ok = "UP".equals(ems) && "UP".equals(gw) && "UP".equals(view); + String ems = check(portalDataSource); + String gw = check(gatewayDataSource); + String view = checkTemplateEngine(); + String logging = checkJbossLogging(); + String guava = checkGuava(); + boolean ok = "UP".equals(ems) && "UP".equals(gw) && "UP".equals(view) + && "UP".equals(logging) && "UP".equals(guava); String body = "{" + "\"status\":\"" + (ok ? "UP" : "DOWN") + "\"," - + "\"ems\":\"" + ems + "\"," - + "\"gateway\":\"" + gw + "\"," - + "\"view\":\"" + view + "\"" + + "\"ems\":\"" + ems + "\"," + + "\"gateway\":\"" + gw + "\"," + + "\"view\":\"" + view + "\"," + + "\"logging\":\"" + logging + "\"," + + "\"guava\":\"" + guava + "\"" + "}"; return ResponseEntity.status(ok ? 200 : 503) @@ -76,11 +91,40 @@ public class ReadinessController { templateEngine.getConfiguration(); return "UP"; } catch (Throwable t) { - String msg = t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); - return "DOWN:" + msg.replace('"', '\'').replace('\n', ' ').replace('\r', ' '); + return down(t); } } + /** Forces jboss-logging's {@code Logger} + its bound provider (LogManager/JBoss/JUL + * discovery) to load and actually run a log-level check, not just resolve the class. */ + private static String checkJbossLogging() { + try { + Logger log = Logger.getLogger(ReadinessController.class); + log.isDebugEnabled(); + return "UP"; + } catch (Throwable t) { + return down(t); + } + } + + /** {@code Futures.immediateFuture} touches Guava's {@code util.concurrent} machinery + * (AbstractFuture/InternalFutureFailureAccess) — the part backed by the separate + * {@code failureaccess} jar, which is exactly where an app/WebLogic version mismatch + * would split across classloaders. */ + private static String checkGuava() { + try { + Futures.immediateFuture(Boolean.TRUE).isDone(); + return "UP"; + } catch (Throwable t) { + return down(t); + } + } + + private static String down(Throwable t) { + String msg = t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); + return "DOWN:" + msg.replace('"', '\'').replace('\n', ' ').replace('\r', ' '); + } + private static String check(DataSource ds) { if (ds == null) return "DOWN:NO_DATASOURCE"; try (Connection c = ds.getConnection()) {