Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ public class EmbeddingModelKeepAlive implements HealthIndicator {
/** Probe latency above this marks a slow provider response without inferring its remote cause. */
private static final long SLOW_PROBE_THRESHOLD_MILLIS = 5_000L;

/** Escalates only after a probe condition repeats on the next observation. */
/**
* Escalates only after a probe condition repeats on the next observation.
*
* <p>The escalation stays at WARN even when repeated: a background probe failure is a
* monitoring signal, never a user-facing request failure, and ERROR lines feed the
* critical java-chat-error-log Grafana alert. Provider-health paging is owned by the
* dependencies health group (java-chat-dependencies-degraded) and the gateway's own
* availability rules, so a routine gateway redeploy must not page here.</p>
Comment on lines +33 to +37
*/
private static final int REPEATED_PROBE_ALERT_COUNT = 2;

private static final long NANOS_PER_MILLISECOND = 1_000_000L;
Expand Down Expand Up @@ -178,7 +186,7 @@ private void recordFailure(
.log(() -> "event=embedding_model_probe_failed outcome=failure model=" + logSafeModelName
+ " durationMs=" + probeDurationMillis + " consecutiveFailures=" + consecutiveFailureCount);
} else if (consecutiveFailureCount == REPEATED_PROBE_ALERT_COUNT) {
log.atError()
log.atWarn()
.setCause(embeddingUnavailableException)
.log(() -> "event=embedding_model_probe_failure_loop outcome=failure model=" + logSafeModelName
+ " durationMs=" + probeDurationMillis + " consecutiveFailures=" + consecutiveFailureCount);
Expand Down
12 changes: 10 additions & 2 deletions src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@ spring.thymeleaf.cache=false
logging.level.org.springframework.boot.devtools=DEBUG

# Development-specific overrides
# More verbose logging for development
# More verbose logging for development.
#
# `dev` is also the profile the deployed dev.javachat.ai runs under, so anything set here ships
# to Loki. org.springframework.web at DEBUG was 99.5% of this service's log volume — a 5,000-line
# sample over 3h on 2026-08-04 held 1,991 DispatcherServlet + 1,990 HttpEntityMethodProcessor +
# 994 RequestResponseBodyMethodProcessor lines against 24 application lines, essentially all of it
# request plumbing for the 15s /actuator/prometheus scrape. It buried the signal that matters
# (EmbeddingModelKeepAlive, which the java-chat Loki alert rules parse) and cost 73,705 lines/24h
# against production's 1,233. Raise it locally when debugging Spring MVC itself, e.g.
# `--logging.level.org.springframework.web=DEBUG`, rather than for every deployed request.
Comment on lines +42 to +51
logging.level.com.williamcallahan.javachat=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.ai=DEBUG

# CORS for Vite dev server (port 5173) + local Spring Boot (port 8085) + deployed dev host.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ void repeatedFailuresEscalateOnceAndRecoveryRestoresHealth() {

assertEquals(Status.DOWN, keepAlive.health().getStatus());
assertEquals(1, eventCount(Level.WARN, "event=embedding_model_probe_failed"));
assertEquals(1, eventCount(Level.ERROR, "event=embedding_model_probe_failure_loop"));
assertEquals(1, eventCount(Level.WARN, "event=embedding_model_probe_failure_loop"));
assertEquals(0, eventCount(Level.ERROR, "event=embedding_model_probe_"));

keepAlive.retryUnavailableEmbeddingModel();

Expand Down