This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/main by this push:
     new a4632a181bb CAMEL-24512: camel-spring-boot - keep each failing health 
check's error message
a4632a181bb is described below

commit a4632a181bbc6c315b8bfa57e6bb2ec914ff394b
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Sep 2 15:28:03 2026 +0200

    CAMEL-24512: camel-spring-boot - keep each failing health check's error 
message
    
    CamelHealthCheckIndicator applies every health check result to the same
    Health.Builder, and error.message was written only as a flat top-level 
detail.
    Health.Builder.withDetail is a Map.put, so with more than one DOWN check 
the last
    result overwrote the messages of all the earlier ones.
    
    The message is now also written into the check-scoped <id>.data map, 
alongside
    the other per-check entries, so each failing check keeps its own. The 
top-level
    key is left in place for compatibility.
    
    The diff is deliberately confined to error.message: PR #1929 (CAMEL-24592)
    changes the stack-trace gating in the same method.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_0165HC1XCB3h6mMii6wbZneG
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../boot/actuate/health/CamelHealthHelper.java     |  4 +
 .../CamelHealthHelperMultipleFailuresTest.java     | 86 ++++++++++++++++++++++
 2 files changed, 90 insertions(+)

diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelper.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelper.java
index fe50d3e781f..7db55b403f4 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelper.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelper.java
@@ -61,7 +61,11 @@ final class CamelHealthHelper {
 
             result.getError().ifPresent(error -> {
                 if (error.getMessage() != null) {
+                    // the top-level key is kept for compatibility, but it is 
a single slot on a builder shared
+                    // by every check, so with more than one DOWN check the 
last one wins; the check-scoped copy
+                    // is the one that stays addressable per check
                     builder.withDetail("error.message", error.getMessage());
+                    data.put("error.message", error.getMessage());
                 }
                 // the stack trace is the most verbose detail there is, so 
only include it in full exposure level
                 if (exposureLevel.equals("full")) {
diff --git 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelperMultipleFailuresTest.java
 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelperMultipleFailuresTest.java
new file mode 100644
index 00000000000..bb78f62a4e2
--- /dev/null
+++ 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelperMultipleFailuresTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spring.boot.actuate.health;
+
+import org.apache.camel.health.HealthCheck;
+import org.apache.camel.health.HealthCheckResultBuilder;
+import org.apache.camel.impl.health.AbstractHealthCheck;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.health.contributor.Health;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * The indicator applies every health check result to one {@link 
Health.Builder}, so a detail written at the top level
+ * is a single slot shared by all of them. Each failing check has to keep its 
own message addressable.
+ */
+public class CamelHealthHelperMultipleFailuresTest {
+
+    private static final class TestHealthCheck extends AbstractHealthCheck {
+        private TestHealthCheck(String id) {
+            super("test", id);
+        }
+
+        @Override
+        protected void doCall(HealthCheckResultBuilder builder, Map<String, 
Object> options) {
+            builder.down();
+        }
+    }
+
+    private static HealthCheck.Result downResult(String id, Throwable error) {
+        return HealthCheckResultBuilder.on(new 
TestHealthCheck(id)).down().error(error).build();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Map<String, String> checkData(Health health, String id) {
+        Object data = health.getDetails().get(id + ".data");
+        assertNotNull(data, "expected per-check data for " + id + ", details 
were: " + health.getDetails());
+        assertInstanceOf(Map.class, data);
+        return (Map<String, String>) data;
+    }
+
+    @Test
+    public void eachFailingCheckKeepsItsOwnMessage() {
+        Health.Builder builder = new Health.Builder();
+
+        CamelHealthHelper.applyHealthDetail(builder,
+                downResult("first", new 
IllegalArgumentException("first-message")), "default");
+        CamelHealthHelper.applyHealthDetail(builder,
+                downResult("second", new 
IllegalStateException("second-message")), "default");
+
+        Health health = builder.build();
+
+        assertEquals("first-message", checkData(health, 
"first").get("error.message"),
+                "the first check's message must survive a later failing 
check");
+        assertEquals("second-message", checkData(health, 
"second").get("error.message"));
+    }
+
+    @Test
+    public void topLevelErrorMessageIsStillReported() {
+        Health.Builder builder = new Health.Builder();
+
+        CamelHealthHelper.applyHealthDetail(builder,
+                downResult("only", new IllegalStateException("the-message")), 
"default");
+
+        assertEquals("the-message", 
builder.build().getDetails().get("error.message"),
+                "the top-level key is kept for compatibility");
+    }
+}

Reply via email to