diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.json b/core/camel-spring-boot/src/main/docs/spring-boot.json index 5c360d573e62..29389f134676 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.json +++ b/core/camel-spring-boot/src/main/docs/spring-boot.json @@ -378,7 +378,7 @@ { "name": "camel.health.exposure-level", "type": "java.lang.String", - "description": "Sets the level of details to exposure as result of invoking health checks. There are the following levels: full, default, oneline The full level will include all details and status from all the invoked health checks. The default level will report UP if everything is okay, and only include detailed information for health checks that was DOWN. The oneline level will only report either UP or DOWN.", + "description": "Sets the level of details to exposure as result of invoking health checks. There are the following levels: full, default, oneline The full level will include all details and status from all the invoked health checks, including the stack trace of any error carried by a health check result. The default level will report UP if everything is okay, and only include detailed information for health checks that was DOWN, such as the error message, but not the stack trace. The oneline level will only report either UP or DOWN.", "sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties", "defaultValue": "default" }, diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java index 350af04f1e75..f6c171c5e2d1 100644 --- a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java @@ -89,10 +89,11 @@ public class CamelHealthCheckConfigurationProperties { * Sets the level of details to exposure as result of invoking health checks. There are the following levels: full, * default, oneline * - * The full level will include all details and status from all the invoked health checks. + * The full level will include all details and status from all the invoked health checks, including the stack trace + * of any error carried by a health check result. * * The default level will report UP if everything is okay, and only include detailed information for health checks - * that was DOWN. + * that was DOWN, such as the error message, but not the stack trace. * * The oneline level will only report either UP or DOWN. */ 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 c6da07f0aded..fe50d3e781f4 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 @@ -63,10 +63,13 @@ public static void applyHealthDetail(Health.Builder builder, HealthCheck.Result if (error.getMessage() != null) { builder.withDetail("error.message", error.getMessage()); } - final StringWriter stackTraceWriter = new StringWriter(); - try (final PrintWriter pw = new PrintWriter(stackTraceWriter, true)) { - error.printStackTrace(pw); - data.put("error.stacktrace", stackTraceWriter.toString()); + // the stack trace is the most verbose detail there is, so only include it in full exposure level + if (exposureLevel.equals("full")) { + final StringWriter stackTraceWriter = new StringWriter(); + try (final PrintWriter pw = new PrintWriter(stackTraceWriter, true)) { + error.printStackTrace(pw); + data.put("error.stacktrace", stackTraceWriter.toString()); + } } }); diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelperTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelperTest.java new file mode 100644 index 000000000000..3ba3bf17fd1d --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/health/CamelHealthHelperTest.java @@ -0,0 +1,103 @@ +/* + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests that the stack trace of a failing health check is only exposed in the full exposure level. + */ +public class CamelHealthHelperTest { + + private static final String MY_CHECK_ID = "my-check"; + + @Test + public void defaultExposureLevelShouldNotIncludeStackTrace() { + Health health = applyDownResult("default"); + + assertEquals("Cannot connect to broker", health.getDetails().get("error.message")); + Map data = data(health); + assertEquals("my-route", data.get("route.id")); + assertFalse(data.containsKey("error.stacktrace"), "Stack trace should not be exposed at default level"); + } + + @Test + public void fullExposureLevelShouldIncludeStackTrace() { + Health health = applyDownResult("full"); + + assertEquals("Cannot connect to broker", health.getDetails().get("error.message")); + Map data = data(health); + assertEquals("my-route", data.get("route.id")); + String stackTrace = data.get("error.stacktrace"); + assertTrue(stackTrace != null && stackTrace.contains("Cannot connect to broker"), + "Stack trace should be exposed at full level"); + assertTrue(stackTrace.contains(CamelHealthHelperTest.class.getName()), "Stack trace should contain the frames"); + } + + @Test + public void onelineExposureLevelShouldNotIncludeAnyDetail() { + Health health = applyDownResult("oneline"); + + assertNull(health.getDetails().get("error.message")); + assertNull(health.getDetails().get(MY_CHECK_ID + ".data")); + } + + private static Health applyDownResult(String exposureLevel) { + HealthCheck check = new MyHealthCheck(); + HealthCheck.Result result = HealthCheckResultBuilder.on(check) + .down() + .error(new IllegalStateException("Cannot connect to broker")) + .detail("route.id", "my-route") + .build(); + + Health.Builder builder = new Health.Builder(); + CamelHealthHelper.applyHealthDetail(builder, result, exposureLevel); + return builder.down().build(); + } + + @SuppressWarnings("unchecked") + private static Map data(Health health) { + Object data = health.getDetails().get(MY_CHECK_ID + ".data"); + assertInstanceOf(Map.class, data, "Expected health check data to be present"); + return (Map) data; + } + + private static final class MyHealthCheck extends AbstractHealthCheck { + + private MyHealthCheck() { + super(MY_CHECK_ID); + } + + @Override + protected void doCall(HealthCheckResultBuilder builder, Map options) { + builder.down(); + } + } + +}