From 533dced95b1087c020fe8128e022b61a6ebf7d8f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 29 Aug 2026 22:14:17 +0000 Subject: [PATCH 1/5] CAMEL-24555: Add Spring Boot config for GenAI observability Expose camel.aiobservability.enabled as first-class Spring Boot configuration and wire it to the Camel PropertiesComponent so GenAiObservability.isEnabled() works consistently with Camel Main. Co-authored-by: Cursor --- core/camel-spring-boot/pom.xml | 4 + .../src/main/docs/spring-boot.adoc | 15 ++++ .../src/main/docs/spring-boot.json | 12 +++ ...CamelAiObservabilityAutoConfiguration.java | 64 +++++++++++++++ ...iObservabilityConfigurationProperties.java | 39 +++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...rvabilityAutoConfigurationDefaultTest.java | 63 +++++++++++++++ ...vabilityAutoConfigurationDisabledTest.java | 65 +++++++++++++++ ...abilityAutoConfigurationKebabCaseTest.java | 61 ++++++++++++++ ...tyAutoConfigurationRelaxedBindingTest.java | 61 ++++++++++++++ ...bservabilityAutoConfigurationUnitTest.java | 79 +++++++++++++++++++ 11 files changed, 464 insertions(+) create mode 100644 core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java create mode 100644 core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java create mode 100644 core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java create mode 100644 core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java create mode 100644 core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java create mode 100644 core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java create mode 100644 core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java diff --git a/core/camel-spring-boot/pom.xml b/core/camel-spring-boot/pom.xml index e580f2ff1a8..3a0e31887ca 100644 --- a/core/camel-spring-boot/pom.xml +++ b/core/camel-spring-boot/pom.xml @@ -94,6 +94,10 @@ org.apache.camel camel-health + + org.apache.camel + camel-ai-observability-api + diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc b/core/camel-spring-boot/src/main/docs/spring-boot.adoc index 63ad5d27530..1c6bcf3b950 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc +++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc @@ -180,6 +180,21 @@ public class MyRouter extends RouteBuilder { } ---- +=== GenAI observability + +Camel 4.23+ AI producers emit OpenTelemetry spans and Micrometer metrics when `camel-ai-observability` and a tracing or +metrics backend are on the classpath. Spring Boot exposes the global toggle as a first-class configuration property: + +[source,properties] +---- +# Disable GenAI observability for all AI producers (default is true) +camel.aiobservability.enabled=false +---- + +Relaxed binding also accepts `camel.aiObservability.enabled` and `camel.ai-observability.enabled`. The property is +written to the Camel `PropertiesComponent` as `camel.aiObservability.enabled`, matching Camel Main and JBang. See the +Camel xref:next@components:others:ai-observability.adoc[AI Observability] documentation for span attributes and metrics. + == Custom Camel context configuration If you would like to perform some operations on `CamelContext` bean 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 5c360d573e6..afce42aaf86 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.json +++ b/core/camel-spring-boot/src/main/docs/spring-boot.json @@ -37,6 +37,11 @@ "sourceType": "org.apache.camel.spring.boot.DataFormatConfigurationProperties", "sourceMethod": "getCustomizer()" }, + { + "name": "camel.aiobservability", + "type": "org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityConfigurationProperties", + "sourceType": "org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityConfigurationProperties" + }, { "name": "camel.errorregistry", "type": "org.apache.camel.spring.boot.errorregistry.CamelErrorRegistryConfigurationProperties", @@ -295,6 +300,13 @@ "sourceType": "org.apache.camel.spring.boot.DataFormatConfigurationProperties", "defaultValue": true }, + { + "name": "camel.aiobservability.enabled", + "type": "java.lang.Boolean", + "description": "Enables GenAI observability for Camel AI producers (OpenTelemetry spans and Micrometer metrics when backends are present).", + "sourceType": "org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityConfigurationProperties", + "defaultValue": true + }, { "name": "camel.errorregistry.body-include-files", "type": "java.lang.Boolean", diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java new file mode 100644 index 00000000000..3dc2ab93cd4 --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java @@ -0,0 +1,64 @@ +/* + * 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.aiobservability; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; + +@AutoConfiguration(after = CamelAutoConfiguration.class) +@ConditionalOnBean(CamelAutoConfiguration.class) +@EnableConfigurationProperties(CamelAiObservabilityConfigurationProperties.class) +public class CamelAiObservabilityAutoConfiguration { + + @Bean + GenAiObservabilityConfigurer genAiObservabilityConfigurer( + CamelContext camelContext, Environment environment, PropertiesComponent propertiesComponent) { + propertiesComponent.setCamelContext(camelContext); + camelContext.setPropertiesComponent(propertiesComponent); + applyEnabledProperty(camelContext, resolveEnabled(environment)); + return new GenAiObservabilityConfigurer(); + } + + static final class GenAiObservabilityConfigurer { + // marker bean + } + + static boolean resolveEnabled(Environment environment) { + Boolean canonical = environment.getProperty(GenAiObservabilityProperties.ENABLED, Boolean.class); + if (canonical != null) { + return canonical; + } + return Binder.get(environment) + .bind("camel.aiobservability", Bindable.of(CamelAiObservabilityConfigurationProperties.class)) + .map(CamelAiObservabilityConfigurationProperties::isEnabled) + .orElse(true); + } + + static void applyEnabledProperty(CamelContext camelContext, boolean enabled) { + PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); + pc.addOverrideProperty(GenAiObservabilityProperties.ENABLED, Boolean.toString(enabled)); + } +} diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java new file mode 100644 index 00000000000..fb358e83ce8 --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java @@ -0,0 +1,39 @@ +/* + * 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.aiobservability; + +import org.apache.camel.spi.Metadata; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "camel.aiobservability") +public class CamelAiObservabilityConfigurationProperties { + + /** + * Enables GenAI observability for Camel AI producers (OpenTelemetry spans and Micrometer metrics when backends are + * present). + */ + @Metadata(defaultValue = "true") + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index bf307457a06..ae9885cc8d1 100644 --- a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -15,6 +15,7 @@ ## limitations under the License. ## --------------------------------------------------------------------------- +org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityAutoConfiguration org.apache.camel.spring.boot.CamelAutoConfiguration org.apache.camel.spring.boot.actuate.console.CamelDevConsoleAutoConfiguration org.apache.camel.spring.boot.actuate.endpoint.CamelRouteControllerEndpointAutoConfiguration diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java new file mode 100644 index 00000000000..cdb8f1edefb --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java @@ -0,0 +1,63 @@ +/* + * 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.aiobservability; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.ai.observability.GenAiObservability; +import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit6.CamelSpringBootTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest(classes = { + CamelAiObservabilityAutoConfigurationDefaultTest.TestConfiguration.class, + CamelAiObservabilityAutoConfiguration.class, + CamelAutoConfiguration.class +}) +class CamelAiObservabilityAutoConfigurationDefaultTest { + + @Autowired + CamelContext camelContext; + + @Autowired + CamelAiObservabilityConfigurationProperties configurationProperties; + + @Test + void shouldEnableGenAiObservabilityByDefault() { + assertThat(configurationProperties.isEnabled()).isTrue(); + assertThat(GenAiObservability.isEnabled(camelContext)).isTrue(); + + PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); + assertThat(pc.getOverrideProperties()) + .containsEntry(GenAiObservabilityProperties.ENABLED, "true"); + } + + @Configuration + static class TestConfiguration { + } +} diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java new file mode 100644 index 00000000000..b840f78c6fb --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java @@ -0,0 +1,65 @@ +/* + * 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.aiobservability; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.ai.observability.GenAiObservability; +import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit6.CamelSpringBootTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest( + classes = { + CamelAiObservabilityAutoConfigurationDisabledTest.TestConfiguration.class, + CamelAiObservabilityAutoConfiguration.class, + CamelAutoConfiguration.class + }, + properties = "camel.aiobservability.enabled=false") +class CamelAiObservabilityAutoConfigurationDisabledTest { + + @Autowired + CamelContext camelContext; + + @Autowired + CamelAiObservabilityConfigurationProperties configurationProperties; + + @Test + void shouldDisableGenAiObservability() { + assertThat(configurationProperties.isEnabled()).isFalse(); + assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); + + PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); + assertThat(pc.getOverrideProperties()) + .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); + } + + @Configuration + static class TestConfiguration { + } +} diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java new file mode 100644 index 00000000000..f70bb3f76bc --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java @@ -0,0 +1,61 @@ +/* + * 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.aiobservability; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.ai.observability.GenAiObservability; +import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit6.CamelSpringBootTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest( + classes = { + CamelAiObservabilityAutoConfigurationKebabCaseTest.TestConfiguration.class, + CamelAiObservabilityAutoConfiguration.class, + CamelAutoConfiguration.class + }, + properties = "camel.ai-observability.enabled=false") +class CamelAiObservabilityAutoConfigurationKebabCaseTest { + + @Autowired + CamelContext camelContext; + + @Test + void shouldDisableGenAiObservabilityWithKebabCaseProperty() { + assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); + + PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); + assertThat(pc.getOverrideProperties()) + .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); + } + + @Configuration + static class TestConfiguration { + } +} diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java new file mode 100644 index 00000000000..ed4f57db54c --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java @@ -0,0 +1,61 @@ +/* + * 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.aiobservability; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.ai.observability.GenAiObservability; +import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit6.CamelSpringBootTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest( + classes = { + CamelAiObservabilityAutoConfigurationRelaxedBindingTest.TestConfiguration.class, + CamelAiObservabilityAutoConfiguration.class, + CamelAutoConfiguration.class + }, + properties = "camel.aiObservability.enabled=false") +class CamelAiObservabilityAutoConfigurationRelaxedBindingTest { + + @Autowired + CamelContext camelContext; + + @Test + void shouldDisableGenAiObservabilityWithCamelCaseProperty() { + assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); + + PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); + assertThat(pc.getOverrideProperties()) + .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); + } + + @Configuration + static class TestConfiguration { + } +} diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java new file mode 100644 index 00000000000..f08fc3923ec --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java @@ -0,0 +1,79 @@ +/* + * 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.aiobservability; + +import org.apache.camel.component.ai.observability.GenAiObservability; +import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.springframework.mock.env.MockEnvironment; + +class CamelAiObservabilityAutoConfigurationUnitTest { + + @Test + void shouldPreferCanonicalPropertyFromEnvironment() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty(GenAiObservabilityProperties.ENABLED, "false"); + + assertThat(CamelAiObservabilityAutoConfiguration.resolveEnabled(environment)).isFalse(); + } + + @Test + void shouldBindKebabCasePropertyFromEnvironment() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("camel.ai-observability.enabled", "false"); + + assertThat(CamelAiObservabilityAutoConfiguration.resolveEnabled(environment)).isFalse(); + } + + @Test + void shouldDefaultToEnabledWhenPropertyIsAbsent() { + MockEnvironment environment = new MockEnvironment(); + + assertThat(CamelAiObservabilityAutoConfiguration.resolveEnabled(environment)).isTrue(); + } + + @Test + void shouldApplyDisabledPropertyToLocalProperties() throws Exception { + try (DefaultCamelContext camelContext = new DefaultCamelContext()) { + camelContext.getPropertiesComponent().setLocalProperties(null); + + CamelAiObservabilityAutoConfiguration.applyEnabledProperty(camelContext, false); + + PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); + assertThat(pc.getOverrideProperties()) + .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); + assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); + } + } + + @Test + void shouldApplyEnabledPropertyToExistingLocalProperties() throws Exception { + try (DefaultCamelContext camelContext = new DefaultCamelContext()) { + CamelAiObservabilityAutoConfiguration.applyEnabledProperty(camelContext, true); + + PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); + assertThat(pc.getOverrideProperties()) + .containsEntry(GenAiObservabilityProperties.ENABLED, "true"); + assertThat(GenAiObservability.isEnabled(camelContext)).isTrue(); + } + } +} From 9892b0cf8699322748995c19de98b82e561068c1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 30 Aug 2026 16:29:08 +0000 Subject: [PATCH 2/5] CAMEL-24555: Move GenAI observability to camel-ai-observability-starter Extract GenAI observability auto-configuration from core/camel-spring-boot into a dedicated camel-ai-observability-starter module so users opt in by adding the dependency (matching camel-opentelemetry2-starter precedent). - Remove mandatory camel-ai-observability-api dependency from core - Use CamelContextConfiguration instead of marker bean - Write camel.aiObservability.enabled to local properties (align with camel-main) - Remove redundant PropertiesComponent wiring - Move tests and configuration metadata to the new starter Co-authored-by: Cursor --- .../camel-ai-observability-starter/pom.xml | 62 ++++++ .../src/main/doc/intro.adoc | 15 ++ .../src/main/docs/ai-observability.json | 22 ++ .../AiObservabilityAutoConfiguration.java | 40 ++-- ...iObservabilityConfigurationProperties.java | 6 +- .../starter/CamelAiObservability.java | 34 +++ .../src/main/resources/META-INF/LICENSE.txt | 203 ++++++++++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + ...ot.autoconfigure.AutoConfiguration.imports | 17 ++ ...rvabilityAutoConfigurationDefaultTest.java | 12 +- ...vabilityAutoConfigurationDisabledTest.java | 12 +- ...abilityAutoConfigurationKebabCaseTest.java | 10 +- ...tyAutoConfigurationRelaxedBindingTest.java | 10 +- ...bservabilityAutoConfigurationUnitTest.java | 21 +- components-starter/pom.xml | 1 + core/camel-spring-boot/pom.xml | 4 - .../src/main/docs/spring-boot.adoc | 16 +- .../src/main/docs/spring-boot.json | 12 -- ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../camel-spring-boot-dependencies/pom.xml | 5 + 20 files changed, 442 insertions(+), 72 deletions(-) create mode 100644 components-starter/camel-ai-observability-starter/pom.xml create mode 100644 components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc create mode 100644 components-starter/camel-ai-observability-starter/src/main/docs/ai-observability.json rename core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java => components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfiguration.java (63%) rename core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java => components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityConfigurationProperties.java (87%) create mode 100644 components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java create mode 100644 components-starter/camel-ai-observability-starter/src/main/resources/META-INF/LICENSE.txt create mode 100644 components-starter/camel-ai-observability-starter/src/main/resources/META-INF/NOTICE.txt create mode 100644 components-starter/camel-ai-observability-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java => components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDefaultTest.java (85%) rename core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java => components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDisabledTest.java (85%) rename core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java => components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationKebabCaseTest.java (87%) rename core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java => components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationRelaxedBindingTest.java (87%) rename core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java => components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationUnitTest.java (80%) diff --git a/components-starter/camel-ai-observability-starter/pom.xml b/components-starter/camel-ai-observability-starter/pom.xml new file mode 100644 index 00000000000..e8c8331bcf0 --- /dev/null +++ b/components-starter/camel-ai-observability-starter/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + + org.apache.camel.springboot + components-starter + 4.23.0-SNAPSHOT + + camel-ai-observability-starter + jar + Camel SB Starters :: AI Observability + Spring-Boot Starter for Camel GenAI Observability + + + org.springframework.boot + spring-boot-starter + ${spring-boot-version} + + + org.apache.camel + camel-ai-observability-api + ${camel-version} + + + + org.apache.camel + camel-test-spring-junit6 + ${camel-version} + test + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot-version} + test + + + + org.apache.camel.springboot + camel-core-starter + + + + diff --git a/components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc b/components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc new file mode 100644 index 00000000000..4e82885d861 --- /dev/null +++ b/components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc @@ -0,0 +1,15 @@ +Spring Boot auto-configuration for Camel GenAI observability. + +Add this starter to opt in to GenAI observability configuration for Camel AI producers. Camel 4.23+ AI producers emit OpenTelemetry spans and Micrometer metrics when `camel-ai-observability` and a tracing or metrics backend are on the classpath. + +Configure the global toggle in `application.properties`: + +[source,properties] +---- +# Disable GenAI observability for all AI producers (default is true) +camel.aiobservability.enabled=false +---- + +Relaxed binding also accepts `camel.aiObservability.enabled` and `camel.ai-observability.enabled`. The property is written to the Camel `PropertiesComponent` local properties as `camel.aiObservability.enabled`, matching Camel Main and JBang. + +Use the `@CamelAiObservability` annotation on your Spring Boot main class or configuration to enable explicitly. diff --git a/components-starter/camel-ai-observability-starter/src/main/docs/ai-observability.json b/components-starter/camel-ai-observability-starter/src/main/docs/ai-observability.json new file mode 100644 index 00000000000..bcb7443c7ab --- /dev/null +++ b/components-starter/camel-ai-observability-starter/src/main/docs/ai-observability.json @@ -0,0 +1,22 @@ +{ + "groups": [ + { + "name": "camel.aiobservability", + "type": "org.apache.camel.ai.observability.starter.AiObservabilityConfigurationProperties", + "sourceType": "org.apache.camel.ai.observability.starter.AiObservabilityConfigurationProperties" + } + ], + "properties": [ + { + "name": "camel.aiobservability.enabled", + "type": "java.lang.Boolean", + "description": "Enables GenAI observability for Camel AI producers (OpenTelemetry spans and Micrometer metrics when backends are present).", + "sourceType": "org.apache.camel.ai.observability.starter.AiObservabilityConfigurationProperties", + "defaultValue": true + } + ], + "hints": [], + "ignored": { + "properties": [] + } +} diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java b/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfiguration.java similarity index 63% rename from core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java rename to components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfiguration.java index 3dc2ab93cd4..73fdac9b945 100644 --- a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfiguration.java +++ b/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfiguration.java @@ -14,12 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.boot.aiobservability; +package org.apache.camel.ai.observability.starter; + +import java.util.Properties; import org.apache.camel.CamelContext; import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.spring.boot.CamelContextConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -30,20 +33,22 @@ @AutoConfiguration(after = CamelAutoConfiguration.class) @ConditionalOnBean(CamelAutoConfiguration.class) -@EnableConfigurationProperties(CamelAiObservabilityConfigurationProperties.class) -public class CamelAiObservabilityAutoConfiguration { +@EnableConfigurationProperties(AiObservabilityConfigurationProperties.class) +public class AiObservabilityAutoConfiguration { @Bean - GenAiObservabilityConfigurer genAiObservabilityConfigurer( - CamelContext camelContext, Environment environment, PropertiesComponent propertiesComponent) { - propertiesComponent.setCamelContext(camelContext); - camelContext.setPropertiesComponent(propertiesComponent); - applyEnabledProperty(camelContext, resolveEnabled(environment)); - return new GenAiObservabilityConfigurer(); - } + CamelContextConfiguration aiObservabilityCamelContextConfiguration(Environment environment) { + boolean enabled = resolveEnabled(environment); + return new CamelContextConfiguration() { + @Override + public void beforeApplicationStart(CamelContext camelContext) { + applyEnabledProperty(camelContext, enabled); + } - static final class GenAiObservabilityConfigurer { - // marker bean + @Override + public void afterApplicationStart(CamelContext camelContext) { + } + }; } static boolean resolveEnabled(Environment environment) { @@ -52,13 +57,18 @@ static boolean resolveEnabled(Environment environment) { return canonical; } return Binder.get(environment) - .bind("camel.aiobservability", Bindable.of(CamelAiObservabilityConfigurationProperties.class)) - .map(CamelAiObservabilityConfigurationProperties::isEnabled) + .bind("camel.aiobservability", Bindable.of(AiObservabilityConfigurationProperties.class)) + .map(AiObservabilityConfigurationProperties::isEnabled) .orElse(true); } static void applyEnabledProperty(CamelContext camelContext, boolean enabled) { PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); - pc.addOverrideProperty(GenAiObservabilityProperties.ENABLED, Boolean.toString(enabled)); + Properties local = pc.getLocalProperties(); + if (local == null) { + local = new Properties(); + pc.setLocalProperties(local); + } + local.setProperty(GenAiObservabilityProperties.ENABLED, Boolean.toString(enabled)); } } diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java b/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityConfigurationProperties.java similarity index 87% rename from core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java rename to components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityConfigurationProperties.java index fb358e83ce8..9b99844f6e2 100644 --- a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityConfigurationProperties.java +++ b/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/AiObservabilityConfigurationProperties.java @@ -14,19 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.boot.aiobservability; +package org.apache.camel.ai.observability.starter; -import org.apache.camel.spi.Metadata; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "camel.aiobservability") -public class CamelAiObservabilityConfigurationProperties { +public class AiObservabilityConfigurationProperties { /** * Enables GenAI observability for Camel AI producers (OpenTelemetry spans and Micrometer metrics when backends are * present). */ - @Metadata(defaultValue = "true") private boolean enabled = true; public boolean isEnabled() { diff --git a/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java b/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java new file mode 100644 index 00000000000..8e13bac7734 --- /dev/null +++ b/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java @@ -0,0 +1,34 @@ +/* + * 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.ai.observability.starter; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(AiObservabilityAutoConfiguration.class) +public @interface CamelAiObservability { +} diff --git a/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/LICENSE.txt b/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 00000000000..6b0b1270ff0 --- /dev/null +++ b/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + diff --git a/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/NOTICE.txt b/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 00000000000..2e215bf2e6b --- /dev/null +++ b/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000000..742afe1bb41 --- /dev/null +++ b/components-starter/camel-ai-observability-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +org.apache.camel.ai.observability.starter.AiObservabilityAutoConfiguration diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDefaultTest.java similarity index 85% rename from core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java rename to components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDefaultTest.java index cdb8f1edefb..3904089b867 100644 --- a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDefaultTest.java +++ b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDefaultTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.boot.aiobservability; +package org.apache.camel.ai.observability.starter; import org.apache.camel.CamelContext; import org.apache.camel.component.ai.observability.GenAiObservability; @@ -35,17 +35,17 @@ @CamelSpringBootTest @EnableAutoConfiguration @SpringBootTest(classes = { - CamelAiObservabilityAutoConfigurationDefaultTest.TestConfiguration.class, - CamelAiObservabilityAutoConfiguration.class, + AiObservabilityAutoConfigurationDefaultTest.TestConfiguration.class, + AiObservabilityAutoConfiguration.class, CamelAutoConfiguration.class }) -class CamelAiObservabilityAutoConfigurationDefaultTest { +class AiObservabilityAutoConfigurationDefaultTest { @Autowired CamelContext camelContext; @Autowired - CamelAiObservabilityConfigurationProperties configurationProperties; + AiObservabilityConfigurationProperties configurationProperties; @Test void shouldEnableGenAiObservabilityByDefault() { @@ -53,7 +53,7 @@ void shouldEnableGenAiObservabilityByDefault() { assertThat(GenAiObservability.isEnabled(camelContext)).isTrue(); PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); - assertThat(pc.getOverrideProperties()) + assertThat(pc.getLocalProperties()) .containsEntry(GenAiObservabilityProperties.ENABLED, "true"); } diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDisabledTest.java similarity index 85% rename from core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java rename to components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDisabledTest.java index b840f78c6fb..9e411c39f62 100644 --- a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationDisabledTest.java +++ b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationDisabledTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.boot.aiobservability; +package org.apache.camel.ai.observability.starter; import org.apache.camel.CamelContext; import org.apache.camel.component.ai.observability.GenAiObservability; @@ -36,18 +36,18 @@ @EnableAutoConfiguration @SpringBootTest( classes = { - CamelAiObservabilityAutoConfigurationDisabledTest.TestConfiguration.class, - CamelAiObservabilityAutoConfiguration.class, + AiObservabilityAutoConfigurationDisabledTest.TestConfiguration.class, + AiObservabilityAutoConfiguration.class, CamelAutoConfiguration.class }, properties = "camel.aiobservability.enabled=false") -class CamelAiObservabilityAutoConfigurationDisabledTest { +class AiObservabilityAutoConfigurationDisabledTest { @Autowired CamelContext camelContext; @Autowired - CamelAiObservabilityConfigurationProperties configurationProperties; + AiObservabilityConfigurationProperties configurationProperties; @Test void shouldDisableGenAiObservability() { @@ -55,7 +55,7 @@ void shouldDisableGenAiObservability() { assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); - assertThat(pc.getOverrideProperties()) + assertThat(pc.getLocalProperties()) .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); } diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationKebabCaseTest.java similarity index 87% rename from core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java rename to components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationKebabCaseTest.java index f70bb3f76bc..cb19bac99ab 100644 --- a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationKebabCaseTest.java +++ b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationKebabCaseTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.boot.aiobservability; +package org.apache.camel.ai.observability.starter; import org.apache.camel.CamelContext; import org.apache.camel.component.ai.observability.GenAiObservability; @@ -36,12 +36,12 @@ @EnableAutoConfiguration @SpringBootTest( classes = { - CamelAiObservabilityAutoConfigurationKebabCaseTest.TestConfiguration.class, - CamelAiObservabilityAutoConfiguration.class, + AiObservabilityAutoConfigurationKebabCaseTest.TestConfiguration.class, + AiObservabilityAutoConfiguration.class, CamelAutoConfiguration.class }, properties = "camel.ai-observability.enabled=false") -class CamelAiObservabilityAutoConfigurationKebabCaseTest { +class AiObservabilityAutoConfigurationKebabCaseTest { @Autowired CamelContext camelContext; @@ -51,7 +51,7 @@ void shouldDisableGenAiObservabilityWithKebabCaseProperty() { assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); - assertThat(pc.getOverrideProperties()) + assertThat(pc.getLocalProperties()) .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); } diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationRelaxedBindingTest.java similarity index 87% rename from core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java rename to components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationRelaxedBindingTest.java index ed4f57db54c..fa14e12668a 100644 --- a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationRelaxedBindingTest.java +++ b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationRelaxedBindingTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.boot.aiobservability; +package org.apache.camel.ai.observability.starter; import org.apache.camel.CamelContext; import org.apache.camel.component.ai.observability.GenAiObservability; @@ -36,12 +36,12 @@ @EnableAutoConfiguration @SpringBootTest( classes = { - CamelAiObservabilityAutoConfigurationRelaxedBindingTest.TestConfiguration.class, - CamelAiObservabilityAutoConfiguration.class, + AiObservabilityAutoConfigurationRelaxedBindingTest.TestConfiguration.class, + AiObservabilityAutoConfiguration.class, CamelAutoConfiguration.class }, properties = "camel.aiObservability.enabled=false") -class CamelAiObservabilityAutoConfigurationRelaxedBindingTest { +class AiObservabilityAutoConfigurationRelaxedBindingTest { @Autowired CamelContext camelContext; @@ -51,7 +51,7 @@ void shouldDisableGenAiObservabilityWithCamelCaseProperty() { assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); - assertThat(pc.getOverrideProperties()) + assertThat(pc.getLocalProperties()) .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); } diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationUnitTest.java similarity index 80% rename from core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java rename to components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationUnitTest.java index f08fc3923ec..4556603c514 100644 --- a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aiobservability/CamelAiObservabilityAutoConfigurationUnitTest.java +++ b/components-starter/camel-ai-observability-starter/src/test/java/org/apache/camel/ai/observability/starter/AiObservabilityAutoConfigurationUnitTest.java @@ -14,26 +14,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spring.boot.aiobservability; +package org.apache.camel.ai.observability.starter; import org.apache.camel.component.ai.observability.GenAiObservability; import org.apache.camel.component.ai.observability.GenAiObservabilityProperties; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.impl.DefaultCamelContext; import org.junit.jupiter.api.Test; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; -import org.springframework.mock.env.MockEnvironment; - -class CamelAiObservabilityAutoConfigurationUnitTest { +class AiObservabilityAutoConfigurationUnitTest { @Test void shouldPreferCanonicalPropertyFromEnvironment() { MockEnvironment environment = new MockEnvironment(); environment.setProperty(GenAiObservabilityProperties.ENABLED, "false"); - assertThat(CamelAiObservabilityAutoConfiguration.resolveEnabled(environment)).isFalse(); + assertThat(AiObservabilityAutoConfiguration.resolveEnabled(environment)).isFalse(); } @Test @@ -41,14 +40,14 @@ void shouldBindKebabCasePropertyFromEnvironment() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("camel.ai-observability.enabled", "false"); - assertThat(CamelAiObservabilityAutoConfiguration.resolveEnabled(environment)).isFalse(); + assertThat(AiObservabilityAutoConfiguration.resolveEnabled(environment)).isFalse(); } @Test void shouldDefaultToEnabledWhenPropertyIsAbsent() { MockEnvironment environment = new MockEnvironment(); - assertThat(CamelAiObservabilityAutoConfiguration.resolveEnabled(environment)).isTrue(); + assertThat(AiObservabilityAutoConfiguration.resolveEnabled(environment)).isTrue(); } @Test @@ -56,10 +55,10 @@ void shouldApplyDisabledPropertyToLocalProperties() throws Exception { try (DefaultCamelContext camelContext = new DefaultCamelContext()) { camelContext.getPropertiesComponent().setLocalProperties(null); - CamelAiObservabilityAutoConfiguration.applyEnabledProperty(camelContext, false); + AiObservabilityAutoConfiguration.applyEnabledProperty(camelContext, false); PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); - assertThat(pc.getOverrideProperties()) + assertThat(pc.getLocalProperties()) .containsEntry(GenAiObservabilityProperties.ENABLED, "false"); assertThat(GenAiObservability.isEnabled(camelContext)).isFalse(); } @@ -68,10 +67,10 @@ void shouldApplyDisabledPropertyToLocalProperties() throws Exception { @Test void shouldApplyEnabledPropertyToExistingLocalProperties() throws Exception { try (DefaultCamelContext camelContext = new DefaultCamelContext()) { - CamelAiObservabilityAutoConfiguration.applyEnabledProperty(camelContext, true); + AiObservabilityAutoConfiguration.applyEnabledProperty(camelContext, true); PropertiesComponent pc = (PropertiesComponent) camelContext.getPropertiesComponent(); - assertThat(pc.getOverrideProperties()) + assertThat(pc.getLocalProperties()) .containsEntry(GenAiObservabilityProperties.ENABLED, "true"); assertThat(GenAiObservability.isEnabled(camelContext)).isTrue(); } diff --git a/components-starter/pom.xml b/components-starter/pom.xml index 1e3a6b69ab5..107982ac8f6 100644 --- a/components-starter/pom.xml +++ b/components-starter/pom.xml @@ -107,6 +107,7 @@ camel-activemq-starter camel-activemq6-starter + camel-ai-observability-starter camel-ai-tool-starter camel-amqp-starter camel-arangodb-starter diff --git a/core/camel-spring-boot/pom.xml b/core/camel-spring-boot/pom.xml index 3a0e31887ca..e580f2ff1a8 100644 --- a/core/camel-spring-boot/pom.xml +++ b/core/camel-spring-boot/pom.xml @@ -94,10 +94,6 @@ org.apache.camel camel-health - - org.apache.camel - camel-ai-observability-api - diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc b/core/camel-spring-boot/src/main/docs/spring-boot.adoc index 1c6bcf3b950..d078276b575 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc +++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc @@ -183,7 +183,16 @@ public class MyRouter extends RouteBuilder { === GenAI observability Camel 4.23+ AI producers emit OpenTelemetry spans and Micrometer metrics when `camel-ai-observability` and a tracing or -metrics backend are on the classpath. Spring Boot exposes the global toggle as a first-class configuration property: +metrics backend are on the classpath. Add the `camel-ai-observability-starter` dependency to opt in to first-class +Spring Boot configuration for the global toggle: + +[source,xml] +---- + + org.apache.camel.springboot + camel-ai-observability-starter + +---- [source,properties] ---- @@ -192,8 +201,9 @@ camel.aiobservability.enabled=false ---- Relaxed binding also accepts `camel.aiObservability.enabled` and `camel.ai-observability.enabled`. The property is -written to the Camel `PropertiesComponent` as `camel.aiObservability.enabled`, matching Camel Main and JBang. See the -Camel xref:next@components:others:ai-observability.adoc[AI Observability] documentation for span attributes and metrics. +written to the Camel `PropertiesComponent` local properties as `camel.aiObservability.enabled`, matching Camel Main and +JBang. See the Camel xref:next@components:others:ai-observability.adoc[AI Observability] documentation for span +attributes and metrics. == Custom Camel context configuration 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 afce42aaf86..5c360d573e6 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.json +++ b/core/camel-spring-boot/src/main/docs/spring-boot.json @@ -37,11 +37,6 @@ "sourceType": "org.apache.camel.spring.boot.DataFormatConfigurationProperties", "sourceMethod": "getCustomizer()" }, - { - "name": "camel.aiobservability", - "type": "org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityConfigurationProperties", - "sourceType": "org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityConfigurationProperties" - }, { "name": "camel.errorregistry", "type": "org.apache.camel.spring.boot.errorregistry.CamelErrorRegistryConfigurationProperties", @@ -300,13 +295,6 @@ "sourceType": "org.apache.camel.spring.boot.DataFormatConfigurationProperties", "defaultValue": true }, - { - "name": "camel.aiobservability.enabled", - "type": "java.lang.Boolean", - "description": "Enables GenAI observability for Camel AI producers (OpenTelemetry spans and Micrometer metrics when backends are present).", - "sourceType": "org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityConfigurationProperties", - "defaultValue": true - }, { "name": "camel.errorregistry.body-include-files", "type": "java.lang.Boolean", diff --git a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index ae9885cc8d1..bf307457a06 100644 --- a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -15,7 +15,6 @@ ## limitations under the License. ## --------------------------------------------------------------------------- -org.apache.camel.spring.boot.aiobservability.CamelAiObservabilityAutoConfiguration org.apache.camel.spring.boot.CamelAutoConfiguration org.apache.camel.spring.boot.actuate.console.CamelDevConsoleAutoConfiguration org.apache.camel.spring.boot.actuate.endpoint.CamelRouteControllerEndpointAutoConfiguration diff --git a/tooling/camel-spring-boot-dependencies/pom.xml b/tooling/camel-spring-boot-dependencies/pom.xml index 11ee7a3a7f3..4078fc188a9 100644 --- a/tooling/camel-spring-boot-dependencies/pom.xml +++ b/tooling/camel-spring-boot-dependencies/pom.xml @@ -357,6 +357,11 @@ camel-activemq6-starter ${project.version} + + org.apache.camel.springboot + camel-ai-observability-starter + ${project.version} + org.apache.camel.springboot camel-ai-tool-starter From d4aa5bd19a94c242f6c5a6341e4c2a67dabed46f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 30 Aug 2026 16:57:22 +0000 Subject: [PATCH 3/5] CAMEL-24555: Add BOM and catalog registration for ai-observability starter - Register camel-ai-observability-starter in camel-spring-boot-bom - Add others/ai-observability.json catalog descriptor and others.properties entry - Remove redundant @CamelAiObservability annotation (auto-config via imports) - Fix spring-boot.adoc xref to match existing components convention Co-authored-by: Cursor --- .../springboot/catalog/others.properties | 1 + .../catalog/others/ai-observability.json | 15 ++++++++ .../src/main/doc/intro.adoc | 2 -- .../starter/CamelAiObservability.java | 34 ------------------- .../src/main/docs/spring-boot.adoc | 2 +- tooling/camel-spring-boot-bom/pom.xml | 5 +++ 6 files changed, 22 insertions(+), 37 deletions(-) create mode 100644 catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others/ai-observability.json delete mode 100644 components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java diff --git a/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties index abcbd945fd7..a1feff7fbfe 100644 --- a/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties +++ b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties @@ -1,3 +1,4 @@ +ai-observability aws-xray cli-connector cli-debug diff --git a/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others/ai-observability.json b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others/ai-observability.json new file mode 100644 index 00000000000..87d583a2811 --- /dev/null +++ b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others/ai-observability.json @@ -0,0 +1,15 @@ +{ + "other": { + "kind": "other", + "name": "ai-observability", + "title": "AI Observability", + "description": "Spring Boot auto-configuration for Camel GenAI observability", + "deprecated": false, + "firstVersion": "4.23.0", + "label": "monitoring,ai", + "supportLevel": "Preview", + "groupId": "org.apache.camel.springboot", + "artifactId": "camel-ai-observability-starter", + "version": "4.23.0-SNAPSHOT" + } +} diff --git a/components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc b/components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc index 4e82885d861..419294225a3 100644 --- a/components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc +++ b/components-starter/camel-ai-observability-starter/src/main/doc/intro.adoc @@ -11,5 +11,3 @@ camel.aiobservability.enabled=false ---- Relaxed binding also accepts `camel.aiObservability.enabled` and `camel.ai-observability.enabled`. The property is written to the Camel `PropertiesComponent` local properties as `camel.aiObservability.enabled`, matching Camel Main and JBang. - -Use the `@CamelAiObservability` annotation on your Spring Boot main class or configuration to enable explicitly. diff --git a/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java b/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java deleted file mode 100644 index 8e13bac7734..00000000000 --- a/components-starter/camel-ai-observability-starter/src/main/java/org/apache/camel/ai/observability/starter/CamelAiObservability.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.ai.observability.starter; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.context.annotation.Import; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@Import(AiObservabilityAutoConfiguration.class) -public @interface CamelAiObservability { -} diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc b/core/camel-spring-boot/src/main/docs/spring-boot.adoc index d078276b575..83eb8749b91 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc +++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc @@ -202,7 +202,7 @@ camel.aiobservability.enabled=false Relaxed binding also accepts `camel.aiObservability.enabled` and `camel.ai-observability.enabled`. The property is written to the Camel `PropertiesComponent` local properties as `camel.aiObservability.enabled`, matching Camel Main and -JBang. See the Camel xref:next@components:others:ai-observability.adoc[AI Observability] documentation for span +JBang. See the Camel xref:components:others:ai-observability.adoc[AI Observability] documentation for span attributes and metrics. == Custom Camel context configuration diff --git a/tooling/camel-spring-boot-bom/pom.xml b/tooling/camel-spring-boot-bom/pom.xml index ca1384c0c76..3603059a91c 100644 --- a/tooling/camel-spring-boot-bom/pom.xml +++ b/tooling/camel-spring-boot-bom/pom.xml @@ -43,6 +43,11 @@ camel-activemq6-starter 4.23.0-SNAPSHOT + + org.apache.camel.springboot + camel-ai-observability-starter + 4.23.0-SNAPSHOT + org.apache.camel.springboot camel-ai-tool-starter From b9de893664d9934e9271067c7f8f0fd9f45da73b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 30 Aug 2026 19:25:01 +0000 Subject: [PATCH 4/5] CAMEL-24555: Regenerate starter doc page and nav entry Commit generated docs/spring-boot/modules/ROOT/pages/starters/ai-observability.adoc and the corresponding nav.adoc entry from update-starter-doc-page. Addresses review feedback on apache/camel-spring-boot#1918. Co-authored-by: Cursor Agent --- docs/spring-boot/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/starters/ai-observability.adoc | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 docs/spring-boot/modules/ROOT/pages/starters/ai-observability.adoc diff --git a/docs/spring-boot/modules/ROOT/nav.adoc b/docs/spring-boot/modules/ROOT/nav.adoc index bc967055613..c70719de448 100644 --- a/docs/spring-boot/modules/ROOT/nav.adoc +++ b/docs/spring-boot/modules/ROOT/nav.adoc @@ -6,6 +6,7 @@ // starters: START ** xref:starters/activemq.adoc[ActiveMQ 5.x] ** xref:starters/activemq6.adoc[ActiveMQ 6.x] +** xref:starters/ai-observability.adoc[Ai Observability] ** xref:starters/ai-tool.adoc[AI Tool] ** xref:starters/amqp.adoc[AMQP] ** xref:starters/arangodb.adoc[ArangoDb] diff --git a/docs/spring-boot/modules/ROOT/pages/starters/ai-observability.adoc b/docs/spring-boot/modules/ROOT/pages/starters/ai-observability.adoc new file mode 100644 index 00000000000..ecadc447d40 --- /dev/null +++ b/docs/spring-boot/modules/ROOT/pages/starters/ai-observability.adoc @@ -0,0 +1,38 @@ +// Do not edit directly! +// This file was generated by camel-spring-boot-generator-maven-plugin += Ai Observability +:artifactid: camel-ai-observability-starter + +Spring Boot auto-configuration for Camel GenAI observability. + +Add this starter to opt in to GenAI observability configuration for Camel AI producers. Camel 4.23+ AI producers emit OpenTelemetry spans and Micrometer metrics when `camel-ai-observability` and a tracing or metrics backend are on the classpath. + +Configure the global toggle in `application.properties`: + +[source,properties] +---- +# Disable GenAI observability for all AI producers (default is true) +camel.aiobservability.enabled=false +---- + +Relaxed binding also accepts `camel.aiObservability.enabled` and `camel.ai-observability.enabled`. The property is written to the Camel `PropertiesComponent` local properties as `camel.aiObservability.enabled`, matching Camel Main and JBang. + +== Maven coordinates + +[source,xml] +---- + + org.apache.camel.springboot + camel-ai-observability-starter + +---- + +== Spring Boot Auto-Configuration + +The starter supports 1 options, which are listed below. + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| camel.aiobservability.enabled | Enables GenAI observability for Camel AI producers (OpenTelemetry spans and Micrometer metrics when backends are present). | true | Boolean +|=== From 23817cb34c8e68608c37ff38b348f71b54c969d2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 30 Aug 2026 19:42:49 +0000 Subject: [PATCH 5/5] CAMEL-24555: Remove duplicate GenAI observability section from spring-boot.adoc The starter has its own generated doc page (starters/ai-observability.adoc); drop the redundant section from the core Spring Boot guide per review. Co-authored-by: Cursor --- .../src/main/docs/spring-boot.adoc | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc b/core/camel-spring-boot/src/main/docs/spring-boot.adoc index 83eb8749b91..63ad5d27530 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc +++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc @@ -180,31 +180,6 @@ public class MyRouter extends RouteBuilder { } ---- -=== GenAI observability - -Camel 4.23+ AI producers emit OpenTelemetry spans and Micrometer metrics when `camel-ai-observability` and a tracing or -metrics backend are on the classpath. Add the `camel-ai-observability-starter` dependency to opt in to first-class -Spring Boot configuration for the global toggle: - -[source,xml] ----- - - org.apache.camel.springboot - camel-ai-observability-starter - ----- - -[source,properties] ----- -# Disable GenAI observability for all AI producers (default is true) -camel.aiobservability.enabled=false ----- - -Relaxed binding also accepts `camel.aiObservability.enabled` and `camel.ai-observability.enabled`. The property is -written to the Camel `PropertiesComponent` local properties as `camel.aiObservability.enabled`, matching Camel Main and -JBang. See the Camel xref:components:others:ai-observability.adoc[AI Observability] documentation for span -attributes and metrics. - == Custom Camel context configuration If you would like to perform some operations on `CamelContext` bean