This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 10bb7ca2411f386cf1e091601ea24835f1882faa Author: Pasquale Congiusti <[email protected]> AuthorDate: Mon Feb 24 13:01:20 2025 +0100 feat(components): Camel Opentemeletry 2 New component based on the camel-telemetry abstraction Ref CAMEL-21786 --- bom/camel-bom/pom.xml | 10 + catalog/camel-allcomponents/pom.xml | 5 + .../main/camel-main-configuration-metadata.json | 6 + .../org/apache/camel/catalog/others.properties | 1 + .../camel/catalog/others/opentelemetry2.json | 15 ++ components/camel-opentelemetry2/pom.xml | 118 ++++++++++ .../OpenTelemetryTracerConfigurer.java | 69 ++++++ ...apache.camel.opentelemetry2.OpenTelemetryTracer | 2 + .../org/apache/camel/opentelemetry-tracer-2 | 2 + .../services/org/apache/camel/other.properties | 7 + .../src/generated/resources/opentelemetry2.json | 15 ++ .../src/main/docs/opentelemetry2.adoc | 82 +++++++ .../opentelemetry2/OpenTelemetrySpanAdapter.java | 122 ++++++++++ .../camel/opentelemetry2/OpenTelemetryTracer.java | 169 ++++++++++++++ .../apache/camel/opentelemetry2/AsyncCXFTest.java | 183 +++++++++++++++ .../camel/opentelemetry2/AsyncDirectTest.java | 173 ++++++++++++++ .../camel/opentelemetry2/AsyncWiretapTest.java | 175 +++++++++++++++ .../CamelOpenTelemetryExtension.java | 248 +++++++++++++++++++++ .../camel/opentelemetry2/DisableEndpointTest.java | 89 ++++++++ .../camel/opentelemetry2/EnableProcessorsTest.java | 118 ++++++++++ .../opentelemetry2/OpenTelemetryTracerTest.java | 131 +++++++++++ .../OpenTelemetryTracerTestSupport.java | 44 ++++ .../camel/opentelemetry2/SpanPropagationTest.java | 92 ++++++++ .../src/test/resources/log4j2.properties | 45 ++++ components/pom.xml | 1 + .../Otel2ConfigurationPropertiesConfigurer.java | 85 +++++++ .../camel-main-configuration-metadata.json | 6 + ....apache.camel.main.Otel2ConfigurationProperties | 2 + core/camel-main/src/main/docs/main.adoc | 21 +- .../org/apache/camel/main/BaseMainSupport.java | 55 ++++- .../camel/main/MainConfigurationProperties.java | 16 ++ .../camel/main/Otel2ConfigurationProperties.java | 154 +++++++++++++ .../others/examples/json/opentelemetry2.json | 1 + docs/components/modules/others/nav.adoc | 1 + .../modules/others/pages/opentelemetry2.adoc | 1 + parent/pom.xml | 10 + .../maven/packaging/PrepareCamelMainMojo.java | 7 +- 37 files changed, 2278 insertions(+), 3 deletions(-) diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index 7cbcc7e5e7a..55c538a631d 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -1587,6 +1587,16 @@ <artifactId>camel-opentelemetry</artifactId> <version>4.11.0-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-opentelemetry-2</artifactId> + <version>4.11.0-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-opentelemetry2</artifactId> + <version>4.11.0-SNAPSHOT</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-optaplanner</artifactId> diff --git a/catalog/camel-allcomponents/pom.xml b/catalog/camel-allcomponents/pom.xml index f1a60cc6cc0..7a95d3a5766 100644 --- a/catalog/camel-allcomponents/pom.xml +++ b/catalog/camel-allcomponents/pom.xml @@ -1401,6 +1401,11 @@ <artifactId>camel-opentelemetry</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-opentelemetry2</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-optaplanner</artifactId> diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json index 0c820eb09e6..626329f7b43 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json @@ -17,6 +17,7 @@ { "name": "camel.vault.kubernetescm", "description": "Camel Kubernetes Configmaps Vault configurations", "sourceType": "org.apache.camel.vault.KubernetesConfigMapVaultConfiguration" }, { "name": "camel.vault.hashicorp", "description": "Camel Hashicorp Vault configurations", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration" }, { "name": "camel.opentelemetry", "description": "Camel OpenTelemetry configurations", "sourceType": "org.apache.camel.main.OtelConfigurationProperties" }, + { "name": "camel.opentelemetry2", "description": "Camel OpenTelemetry 2 configurations", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties" }, { "name": "camel.telemetryDev", "description": "Camel Telemetry Dev configurations", "sourceType": "org.apache.camel.main.TelemetryDevConfigurationProperties" }, { "name": "camel.metrics", "description": "Camel Micrometer Metrics configurations", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties" }, { "name": "camel.faulttolerance", "description": "Fault Tolerance EIP Circuit Breaker configurations", "sourceType": "org.apache.camel.main.FaultToleranceConfigurationProperties" }, @@ -205,6 +206,11 @@ { "name": "camel.opentelemetry.excludePatterns", "description": "Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns can be separated by comma.", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, { "name": "camel.opentelemetry.instrumentationName", "description": "A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully qualified class name. Must not be null.", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "camel" }, { "name": "camel.opentelemetry.traceProcessors", "description": "Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern property to filter out Processors.", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.opentelemetry2.enabled", "description": "To enable OpenTelemetry 2", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.opentelemetry2.encoding", "description": "Sets whether the header keys need to be encoded (connector specific) or not. The value is a boolean. Dashes need for instances to be encoded for JMS property keys.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.opentelemetry2.excludePatterns", "description": "Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns can be separated by comma.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, + { "name": "camel.opentelemetry2.instrumentationName", "description": "A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully qualified class name. Must not be null.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "camel" }, + { "name": "camel.opentelemetry2.traceProcessors", "description": "Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern property to filter out Processors.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.resilience4j.automaticTransitionFromOpenToHalfOpenEnabled", "description": "Enables automatic transition from OPEN to HALF_OPEN state once the waitDurationInOpenState has passed.", "sourceType": "org.apache.camel.main.Resilience4jConfigurationProperties", "type": "boolean", "javaType": "java.lang.Boolean", "defaultValue": "false" }, { "name": "camel.resilience4j.bulkheadEnabled", "description": "Whether bulkhead is enabled or not on the circuit breaker.", "sourceType": "org.apache.camel.main.Resilience4jConfigurationProperties", "type": "boolean", "javaType": "java.lang.Boolean", "defaultValue": false }, { "name": "camel.resilience4j.bulkheadMaxConcurrentCalls", "description": "Configures the max amount of concurrent calls the bulkhead will support.", "sourceType": "org.apache.camel.main.Resilience4jConfigurationProperties", "type": "integer", "javaType": "java.lang.Integer" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties index 1c26a3657eb..dca62a83195 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties @@ -34,6 +34,7 @@ observation openapi-java openapi-validator opentelemetry +opentelemetry2 platform-http-jolokia platform-http-main platform-http-vertx diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/opentelemetry2.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/opentelemetry2.json new file mode 100644 index 00000000000..39e3d92475b --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/opentelemetry2.json @@ -0,0 +1,15 @@ +{ + "other": { + "kind": "other", + "name": "opentelemetry2", + "title": "Opentelemetry2", + "description": "Implementation of Camel Opentelemetry based on the Camel Telemetry spec", + "deprecated": false, + "firstVersion": "4.11.0", + "label": "monitoring,microservice", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-opentelemetry2", + "version": "4.11.0-SNAPSHOT" + } +} diff --git a/components/camel-opentelemetry2/pom.xml b/components/camel-opentelemetry2/pom.xml new file mode 100644 index 00000000000..5c536fbb8dd --- /dev/null +++ b/components/camel-opentelemetry2/pom.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>components</artifactId> + <version>4.11.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-opentelemetry2</artifactId> + <packaging>jar</packaging> + <name>Camel :: Opentelemetry 2</name> + <description>Implementation of Camel Opentelemetry based on the Camel Telemetry spec</description> + + <properties> + <firstVersion>4.11.0</firstVersion> + <label>monitoring,microservice</label> + <title>Opentelemetry2</title> + <supportLevel>Preview</supportLevel> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-bom</artifactId> + <version>${opentelemetry-version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-telemetry</artifactId> + </dependency> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-sdk</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-spring-junit5</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-sdk-testing</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.opentelemetry.instrumentation</groupId> + <artifactId>opentelemetry-log4j-appender-2.17</artifactId> + <version>${opentelemetry-log4j2-version}</version> + <scope>test</scope> + </dependency> + + <!-- Required to test CXF async --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-cxf-rest</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-cxf-common</artifactId> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-transports-http-undertow</artifactId> + <version>${cxf-version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>io.undertow</groupId> + <artifactId>undertow-servlet</artifactId> + </exclusion> + <exclusion> + <groupId>io.undertow</groupId> + <artifactId>undertow-servlet-jakarta</artifactId> + </exclusion> + <exclusion> + <groupId>io.undertow</groupId> + <artifactId>undertow-core</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-undertow</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/components/camel-opentelemetry2/src/generated/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerConfigurer.java b/components/camel-opentelemetry2/src/generated/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerConfigurer.java new file mode 100644 index 00000000000..1e97cb58200 --- /dev/null +++ b/components/camel-opentelemetry2/src/generated/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerConfigurer.java @@ -0,0 +1,69 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.opentelemetry2; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.opentelemetry2.OpenTelemetryTracer; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo") +@SuppressWarnings("unchecked") +public class OpenTelemetryTracerConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + org.apache.camel.opentelemetry2.OpenTelemetryTracer target = (org.apache.camel.opentelemetry2.OpenTelemetryTracer) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "camelcontext": + case "camelContext": target.setCamelContext(property(camelContext, org.apache.camel.CamelContext.class, value)); return true; + case "excludepatterns": + case "excludePatterns": target.setExcludePatterns(property(camelContext, java.lang.String.class, value)); return true; + case "spanlifecyclemanager": + case "spanLifecycleManager": target.setSpanLifecycleManager(property(camelContext, org.apache.camel.telemetry.SpanLifecycleManager.class, value)); return true; + case "traceprocessors": + case "traceProcessors": target.setTraceProcessors(property(camelContext, boolean.class, value)); return true; + default: return false; + } + } + + @Override + public Class<?> getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "camelcontext": + case "camelContext": return org.apache.camel.CamelContext.class; + case "excludepatterns": + case "excludePatterns": return java.lang.String.class; + case "spanlifecyclemanager": + case "spanLifecycleManager": return org.apache.camel.telemetry.SpanLifecycleManager.class; + case "traceprocessors": + case "traceProcessors": return boolean.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + org.apache.camel.opentelemetry2.OpenTelemetryTracer target = (org.apache.camel.opentelemetry2.OpenTelemetryTracer) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "camelcontext": + case "camelContext": return target.getCamelContext(); + case "excludepatterns": + case "excludePatterns": return target.getExcludePatterns(); + case "spanlifecyclemanager": + case "spanLifecycleManager": return target.getSpanLifecycleManager(); + case "traceprocessors": + case "traceProcessors": return target.isTraceProcessors(); + default: return null; + } + } +} + diff --git a/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.opentelemetry2.OpenTelemetryTracer b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.opentelemetry2.OpenTelemetryTracer new file mode 100644 index 00000000000..e94619a797c --- /dev/null +++ b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.opentelemetry2.OpenTelemetryTracer @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.opentelemetry2.OpenTelemetryTracerConfigurer diff --git a/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/opentelemetry-tracer-2 b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/opentelemetry-tracer-2 new file mode 100644 index 00000000000..f454b1c5167 --- /dev/null +++ b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/opentelemetry-tracer-2 @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.opentelemetry2.OpenTelemetryTracer diff --git a/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/other.properties b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/other.properties new file mode 100644 index 00000000000..e3f722054ca --- /dev/null +++ b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/other.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +name=opentelemetry2 +groupId=org.apache.camel +artifactId=camel-opentelemetry2 +version=4.11.0-SNAPSHOT +projectName=Camel :: Opentelemetry 2 +projectDescription=Implementation of Camel Opentelemetry based on the Camel Telemetry spec diff --git a/components/camel-opentelemetry2/src/generated/resources/opentelemetry2.json b/components/camel-opentelemetry2/src/generated/resources/opentelemetry2.json new file mode 100644 index 00000000000..39e3d92475b --- /dev/null +++ b/components/camel-opentelemetry2/src/generated/resources/opentelemetry2.json @@ -0,0 +1,15 @@ +{ + "other": { + "kind": "other", + "name": "opentelemetry2", + "title": "Opentelemetry2", + "description": "Implementation of Camel Opentelemetry based on the Camel Telemetry spec", + "deprecated": false, + "firstVersion": "4.11.0", + "label": "monitoring,microservice", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-opentelemetry2", + "version": "4.11.0-SNAPSHOT" + } +} diff --git a/components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc b/components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc new file mode 100644 index 00000000000..650cf80d288 --- /dev/null +++ b/components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc @@ -0,0 +1,82 @@ += Opentelemetry2 Component +:doctitle: Opentelemetry2 +:shortname: opentelemetry2 +:artifactid: camel-opentelemetry2 +:description: Implementation of Camel Opentelemetry based on the Camel Telemetry spec +:since: 4.11 +:supportlevel: Preview +:tabs-sync-option: + +*Since Camel {since}* + +This module is the implementation of the common `camel-telemetry` interface based on https://opentelemetry.io/[OpenTelemetry] technology. The name used here as `camel-opentelemetry2` is done to distinguish on the existing `camel-opentelemetry` which was based on an older Camel tracing specification. You're invited to start replacing the older `camel-opentelemetry` with this one instead as it may become the default component in future version of Camel. + +NOTE: this component has slight differences compared to the `camel-openetelemetry` and is meant to solve a few inconsistencies identified. + +== Configuration + +The configuration properties for the OpenTelemetry2 tracer are: + +[width="100%",cols="10%,10%,80%",options="header",] +|======================================================================= +|Option |Default |Description +|`excludePatterns` | | Sets exclude pattern(s) that will disable tracing for Camel +messages that matches the pattern. The content is a Set<String> where the key is a pattern. The pattern +uses the rules from Intercept. +|`traceProcessors` | `false` | Setting this to true will create new OpenTelemetry Spans for each Camel Processors. +Use the excludePattern property to filter out Processors +|======================================================================= + +=== Using with standalone Camel + +If you use `camel-main` as standalone Camel, then you can enable and use OpenTelemetry without Java code. + +Add `camel-opentelemetry2` component in your POM, and configure in `application.properties`: + +[source,properties] +---- +camel.opentelemetry2.enabled = true +# you can configure the other options +# camel.opentelemetry2.traceProcessors = true +---- + +When starting the application, you may need to configure a few OpenTelemetry SDK variables, as you can see in te following example: + +```bash +$ java -Dotel.metrics.exporter=none -Dotel.logs.exporter=none -jar my-app.jar +``` + +[[OpenTelemetry-JavaAgent]] +=== Java Agent + +Your application will require a Java agent in order to get the traces generated by the Camel application and push to the tracing server. + +NOTE: certain runtimes (ie, Quarkus) may provide an inner client which pushes traces to the server. Make sure to read each specific Camel runtime documentation. + +Download the https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/[latest version]. + +This package includes the instrumentation agent as well as instrumentation for all supported libraries and all available data exporters. The package provides a completely automatic, out-of-the-box experience. Enable the instrumentation agent using the `-javaagent` flag to the JVM. + +[source,bash] +---- +java -javaagent:path/to/opentelemetry-javaagent.jar \ + -Dotel. ... \ + -jar myapp.jar +---- + +By default, the OpenTelemetry Java agent uses https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/otlp[OTLP exporter] configured to send data to https://github.com/open-telemetry/opentelemetry-collector/blob/main/receiver/otlpreceiver/README.md[OpenTelemetry collector] at `http://localhost:4318`. + +Configuration parameters are passed as Java system properties (`-D` flags) or as environment variables. See https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/agent-config.md[the configuration documentation] for the full list of configuration items. For example: + +[source,bash] +---- +java -javaagent:path/to/opentelemetry-javaagent.jar \ + -Dotel.service.name=your-service-name \ + -Dotel.traces.exporter=otlp \ + -jar myapp.jar +---- + +[[OpenTelemetry-Collection]] +=== Collect OpenTelemetry traces + +OpenTelemetry is a tracing protocol which is implemented by several vendors. You can use the Jaeger project which provides an open source all in one tracing application. See details how to run it in https://www.jaegertracing.io/docs/latest/getting-started/[Jaeger getting started guide]. diff --git a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetrySpanAdapter.java b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetrySpanAdapter.java new file mode 100644 index 00000000000..144a1e6c395 --- /dev/null +++ b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetrySpanAdapter.java @@ -0,0 +1,122 @@ +/* + * 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.opentelemetry2; + +import java.util.Map; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import org.apache.camel.telemetry.TagConstants; + +public class OpenTelemetrySpanAdapter implements org.apache.camel.telemetry.Span { + + private static final String DEFAULT_EVENT_NAME = "log"; + + private final Span otelSpan; + private final Baggage baggage; + private Scope scope; + + protected OpenTelemetrySpanAdapter(Span otelSpan, Baggage baggage) { + this.otelSpan = otelSpan; + this.baggage = baggage; + } + + protected Span getSpan() { + return this.otelSpan; + } + + protected void makeCurrent() { + this.scope = this.otelSpan.makeCurrent(); + } + + protected void end() { + this.otelSpan.end(); + } + + protected void close() { + if (scope != null) { + this.scope.close(); + } + } + + protected Baggage getBaggage() { + return this.baggage; + } + + @Override + public void log(Map<String, String> fields) { + this.otelSpan.addEvent(getEventNameFromFields(fields), convertToAttributes(fields)); + } + + @Override + public void setTag(String key, String value) { + this.otelSpan.setAttribute(key, value); + } + + @Override + public void setComponent(String component) { + this.setTag(TagConstants.COMPONENT, component); + } + + @Override + public void setError(boolean isError) { + this.setTag(TagConstants.ERROR, "" + isError); + } + + private String getEventNameFromFields(Map<String, ?> fields) { + Object eventValue = fields == null ? null : fields.get("event"); + if (eventValue != null) { + return eventValue.toString(); + } + + return DEFAULT_EVENT_NAME; + } + + private Attributes convertToAttributes(Map<String, ?> fields) { + AttributesBuilder attributesBuilder = Attributes.builder(); + + for (Map.Entry<String, ?> entry : fields.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + continue; + } + if (value instanceof Byte + || value instanceof Short + || value instanceof Integer + || value instanceof Long) { + attributesBuilder.put(key, ((Number) value).longValue()); + } else if (value instanceof Float || value instanceof Double) { + attributesBuilder.put(key, ((Number) value).doubleValue()); + } else if (value instanceof Boolean) { + attributesBuilder.put(key, (Boolean) value); + } else { + attributesBuilder.put(key, value.toString()); + } + } + return attributesBuilder.build(); + } + + @Override + public String toString() { + return "OpenTelemetrySpanAdapter [span=" + otelSpan + ", baggage=" + baggage + "]"; + } + +} diff --git a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java new file mode 100644 index 00000000000..2ee1b76c3cc --- /dev/null +++ b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java @@ -0,0 +1,169 @@ +/* + * 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.opentelemetry2; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapGetter; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.spi.Configurer; +import org.apache.camel.spi.annotations.JdkService; +import org.apache.camel.support.CamelContextHelper; +import org.apache.camel.telemetry.Span; +import org.apache.camel.telemetry.SpanContextPropagationExtractor; +import org.apache.camel.telemetry.SpanContextPropagationInjector; +import org.apache.camel.telemetry.SpanLifecycleManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@JdkService("opentelemetry-tracer-2") +@Configurer +@ManagedResource(description = "OpenTelemetry2") +public class OpenTelemetryTracer extends org.apache.camel.telemetry.Tracer { + + private static final Logger LOG = LoggerFactory.getLogger(OpenTelemetryTracer.class); + + private Tracer tracer; + private ContextPropagators contextPropagators; + + @Override + protected void initTracer() { + if (tracer == null) { + this.tracer = CamelContextHelper.findSingleByType(getCamelContext(), Tracer.class); + } + if (tracer == null) { + this.tracer = GlobalOpenTelemetry.get().getTracer("camel"); + } + if (tracer == null) { + throw new RuntimeCamelException("Could not find any Opentelemetry tracer!"); + } + + if (contextPropagators == null) { + contextPropagators = CamelContextHelper.findSingleByType( + getCamelContext(), ContextPropagators.class); + } + if (contextPropagators == null) { + contextPropagators = GlobalOpenTelemetry.get().getPropagators(); + } + if (contextPropagators == null) { + throw new RuntimeCamelException("Could not find any Opentelemetry context propagator!"); + } + + this.setSpanLifecycleManager(new OpentelemetrySpanLifecycleManager(tracer, contextPropagators)); + } + + void setTracer(Tracer tracer) { + this.tracer = tracer; + } + + void setContextPropagators(ContextPropagators cp) { + this.contextPropagators = cp; + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + LOG.info("Opentelemetry2 enabled"); + } + + private class OpentelemetrySpanLifecycleManager implements SpanLifecycleManager { + + private final Tracer tracer; + private final ContextPropagators contextPropagators; + + private OpentelemetrySpanLifecycleManager(Tracer tracer, ContextPropagators contextPropagators) { + this.tracer = tracer; + this.contextPropagators = contextPropagators; + } + + @Override + public Span create(String spanName, Span parent, SpanContextPropagationExtractor extractor) { + SpanBuilder builder = tracer.spanBuilder(spanName); + Baggage baggage = null; + + if (parent != null) { + OpenTelemetrySpanAdapter otelParentSpan = (OpenTelemetrySpanAdapter) parent; + builder = builder.setParent(Context.current().with(otelParentSpan.getSpan())); + baggage = otelParentSpan.getBaggage(); + } else { + /* + * This part is a bit tricky in Opentelemetry. We need to verify if the extractor + * (ie, the Camel Exchange) holds a propagated parent. If it doesn't, then, we must use a null Context. + */ + Context current = null; + if (extractor.get("traceparent") != null) { + current = Context.current(); + } + // Try to get parent from context propagation (upstream traces) + Context ctx = contextPropagators.getTextMapPropagator().extract(current, extractor, + new TextMapGetter<SpanContextPropagationExtractor>() { + @Override + public Iterable<String> keys(SpanContextPropagationExtractor carrier) { + return carrier.keys(); + } + + @Override + public String get(SpanContextPropagationExtractor carrier, String key) { + if (carrier.get(key) == null) { + return null; + } + return carrier.get(key).toString(); + } + }); + builder = builder.setParent(ctx); + } + + return new OpenTelemetrySpanAdapter(builder.startSpan(), baggage); + } + + @Override + public void activate(Span span) { + OpenTelemetrySpanAdapter otelSpan = (OpenTelemetrySpanAdapter) span; + otelSpan.makeCurrent(); + } + + @Override + public void deactivate(Span span) { + OpenTelemetrySpanAdapter otelSpan = (OpenTelemetrySpanAdapter) span; + otelSpan.end(); + } + + @Override + public void close(Span span) { + OpenTelemetrySpanAdapter otelSpan = (OpenTelemetrySpanAdapter) span; + otelSpan.close(); + } + + @Override + public void inject(Span span, SpanContextPropagationInjector injector) { + OpenTelemetrySpanAdapter otelSpan = (OpenTelemetrySpanAdapter) span; + Context ctx = Context.current().with(otelSpan.getSpan()); + if (otelSpan.getBaggage() != null) { + ctx = ctx.with(otelSpan.getBaggage()); + } + contextPropagators.getTextMapPropagator().inject(ctx, injector, + (carrier, key, value) -> carrier.put(key, value)); + } + + } + +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncCXFTest.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncCXFTest.java new file mode 100644 index 00000000000..278fddd9076 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncCXFTest.java @@ -0,0 +1,183 @@ +/* + * 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.opentelemetry2; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace; +import org.apache.camel.telemetry.Op; +import org.apache.camel.test.AvailablePortFinder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * AsyncCXFTest tests the execution of CXF async which was reported as a potential candidate to + * inconsistent Span creation in async mode. + */ +public class AsyncCXFTest extends OpenTelemetryTracerTestSupport { + + private static int cxfPort = AvailablePortFinder.getNextRandomAvailable(); + + @Override + protected CamelContext createCamelContext() throws Exception { + OpenTelemetryTracer tst = new OpenTelemetryTracer(); + tst.setTracer(otelExtension.getOpenTelemetry().getTracer("traceTest")); + tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators()); + CamelContext context = super.createCamelContext(); + CamelContextAware.trySetCamelContext(tst, context); + tst.init(context); + return context; + } + + @Test + void testRouteMultipleRequests() throws InterruptedException, IOException { + int j = 10; + MockEndpoint mock = getMockEndpoint("mock:end"); + mock.expectedMessageCount(j); + mock.setAssertPeriod(5000); + for (int i = 0; i < j; i++) { + context.createProducerTemplate().sendBody("direct:start", "Hello!"); + } + mock.assertIsSatisfied(1000); + Map<String, OtelTrace> traces = otelExtension.getTraces(); + // Each trace should have a unique trace id. It is enough to assert that + // the number of elements in the map is the same of the requests to prove + // all traces have been generated uniquely. + assertEquals(j, traces.size()); + // Each trace should have the same structure + for (OtelTrace trace : traces.values()) { + checkTrace(trace); + } + + } + + private void checkTrace(OtelTrace trace) { + List<SpanData> spans = trace.getSpans(); + assertEquals(8, spans.size()); + SpanData testProducer = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://start", Op.EVENT_SENT); + SpanData direct = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://start", Op.EVENT_RECEIVED); + SpanData directSendTo = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://send", Op.EVENT_SENT); + SpanData directSendFrom = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://send", Op.EVENT_RECEIVED); + SpanData cxfRs = OpenTelemetryTracerTestSupport.getSpan( + spans, + "cxfrs://http://localhost:" + cxfPort + "/rest/helloservice/sayHello?synchronous=false", + Op.EVENT_SENT); + SpanData rest = OpenTelemetryTracerTestSupport.getSpan( + spans, + "rest://post:/rest/helloservice:/sayHello?routeId=direct-hi", + Op.EVENT_RECEIVED); + SpanData log = OpenTelemetryTracerTestSupport.getSpan(spans, "log://hi", Op.EVENT_SENT); + SpanData mock = OpenTelemetryTracerTestSupport.getSpan(spans, "mock://end", Op.EVENT_SENT); + + // Validate span completion + assertTrue(testProducer.hasEnded()); + assertTrue(direct.hasEnded()); + assertTrue(directSendTo.hasEnded()); + assertTrue(directSendFrom.hasEnded()); + assertTrue(cxfRs.hasEnded()); + assertTrue(rest.hasEnded()); + assertTrue(log.hasEnded()); + assertTrue(mock.hasEnded()); + + // Validate same trace + assertEquals(testProducer.getSpanContext().getTraceId(), direct.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), directSendTo.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), directSendFrom.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), cxfRs.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), rest.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), log.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), mock.getSpanContext().getTraceId()); + + // Validate different Exchange ID + assertNotEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + rest.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + direct.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + directSendTo.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + directSendFrom.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + cxfRs.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(rest.getAttributes().get(AttributeKey.stringKey("exchangeId")), + log.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(rest.getAttributes().get(AttributeKey.stringKey("exchangeId")), + mock.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + + // Validate hierarchy + assertFalse(testProducer.getParentSpanContext().isValid()); + assertEquals(testProducer.getSpanContext().getSpanId(), direct.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), directSendTo.getParentSpanContext().getSpanId()); + assertEquals(directSendTo.getSpanContext().getSpanId(), directSendFrom.getParentSpanContext().getSpanId()); + assertEquals(directSendFrom.getSpanContext().getSpanId(), cxfRs.getParentSpanContext().getSpanId()); + assertEquals(cxfRs.getSpanContext().getSpanId(), rest.getParentSpanContext().getSpanId()); + assertEquals(rest.getSpanContext().getSpanId(), log.getParentSpanContext().getSpanId()); + assertEquals(rest.getSpanContext().getSpanId(), mock.getParentSpanContext().getSpanId()); + + // Validate message logging + assertEquals("A direct message", directSendFrom.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + assertEquals("say-hi", rest.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("myRoute") + .to("direct:send"); + + from("direct:send") + .log("A direct message") + .to("cxfrs:http://localhost:" + cxfPort + + "/rest/helloservice/sayHello?synchronous=false"); + + restConfiguration() + .port(cxfPort); + + rest("/rest/helloservice") + .post("/sayHello") + .routeId("rest-GET-say-hi") + .to("direct:hi"); + + from("direct:hi") + .routeId("direct-hi") + .delay(2000) + .log("say-hi") + .to("log:hi") + .to("mock:end"); + } + }; + } + +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncDirectTest.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncDirectTest.java new file mode 100644 index 00000000000..4b010c617e1 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncDirectTest.java @@ -0,0 +1,173 @@ +/* + * 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.opentelemetry2; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace; +import org.apache.camel.telemetry.Op; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AsyncDirectTest extends OpenTelemetryTracerTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + OpenTelemetryTracer tst = new OpenTelemetryTracer(); + tst.setTracer(otelExtension.getOpenTelemetry().getTracer("traceTest")); + tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators()); + CamelContext context = super.createCamelContext(); + CamelContextAware.trySetCamelContext(tst, context); + tst.init(context); + return context; + } + + @Test + void testRouteMultipleRequests() throws InterruptedException, IOException { + int j = 10; + MockEndpoint mock = getMockEndpoint("mock:end"); + mock.expectedMessageCount(j); + mock.setAssertPeriod(5000); + for (int i = 0; i < j; i++) { + context.createProducerTemplate().sendBody("direct:start", "Hello!"); + } + mock.assertIsSatisfied(1000); + Map<String, OtelTrace> traces = otelExtension.getTraces(); + // Each trace should have a unique trace id. It is enough to assert that + // the number of elements in the map is the same of the requests to prove + // all traces have been generated uniquely. + assertEquals(j, traces.size()); + // Each trace should have the same structure + for (OtelTrace trace : traces.values()) { + checkTrace(trace, "Hello!"); + } + + } + + private void checkTrace(OtelTrace trace, String expectedBody) { + List<SpanData> spans = trace.getSpans(); + assertEquals(7, spans.size()); + SpanData testProducer = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://start", Op.EVENT_SENT); + SpanData direct = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://start", Op.EVENT_RECEIVED); + SpanData newDirectTo = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://new", Op.EVENT_SENT); + SpanData log = OpenTelemetryTracerTestSupport.getSpan(spans, "log://info", Op.EVENT_SENT); + SpanData newDirectFrom = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://new", Op.EVENT_RECEIVED); + SpanData newLog = OpenTelemetryTracerTestSupport.getSpan(spans, "log://new", Op.EVENT_SENT); + SpanData newMock = OpenTelemetryTracerTestSupport.getSpan(spans, "mock://end", Op.EVENT_SENT); + + // Validate span completion + assertTrue(testProducer.hasEnded()); + assertTrue(direct.hasEnded()); + assertTrue(newDirectTo.hasEnded()); + assertTrue(log.hasEnded()); + assertTrue(newDirectFrom.hasEnded()); + assertTrue(newLog.hasEnded()); + assertTrue(newMock.hasEnded()); + + // Validate same trace + assertEquals(testProducer.getSpanContext().getTraceId(), direct.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), newDirectTo.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), log.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), newDirectFrom.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), newLog.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), newMock.getSpanContext().getTraceId()); + + // Validate same Exchange ID + // As it's a "direct" component, we expect the logic to happen within the same + // Exchange boundary + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + direct.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + newDirectTo.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + newDirectFrom.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + log.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + newLog.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + newMock.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + + // Validate hierarchy + assertFalse(testProducer.getParentSpanContext().isValid()); + assertEquals(testProducer.getSpanContext().getSpanId(), direct.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), newDirectTo.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), log.getParentSpanContext().getSpanId()); + assertEquals(newDirectTo.getSpanContext().getSpanId(), newDirectFrom.getParentSpanContext().getSpanId()); + assertEquals(newDirectFrom.getSpanContext().getSpanId(), newLog.getParentSpanContext().getSpanId()); + assertEquals(newDirectFrom.getSpanContext().getSpanId(), newMock.getParentSpanContext().getSpanId()); + + // Validate message logging + assertEquals("A direct message", direct.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + assertEquals("A new message", newDirectFrom.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + if (expectedBody == null) { + assertEquals( + "Exchange[ExchangePattern: InOut, BodyType: null, Body: [Body is null]]", + log.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + assertEquals( + "Exchange[ExchangePattern: InOut, BodyType: null, Body: [Body is null]]", + newLog.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + } else { + assertEquals( + "Exchange[ExchangePattern: InOnly, BodyType: String, Body: " + expectedBody + "]", + log.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + assertEquals( + "Exchange[ExchangePattern: InOnly, BodyType: String, Body: " + expectedBody + "]", + newLog.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + } + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("start") + .to("direct:new") + .log("A direct message") + .to("log:info"); + + from("direct:new") + .delay(2000) + .routeId("new") + .log("A new message") + .to("log:new") + .to("mock:end"); + } + }; + } + +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncWiretapTest.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncWiretapTest.java new file mode 100644 index 00000000000..6b1ced29627 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/AsyncWiretapTest.java @@ -0,0 +1,175 @@ +/* + * 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.opentelemetry2; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace; +import org.apache.camel.telemetry.Op; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * WiretappedRouteTest tests the execution of a new spin off component which would create a new exchange, for example, + * using the wiretap component. + */ +public class AsyncWiretapTest extends OpenTelemetryTracerTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + OpenTelemetryTracer tst = new OpenTelemetryTracer(); + tst.setTracer(otelExtension.getOpenTelemetry().getTracer("traceTest")); + tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators()); + CamelContext context = super.createCamelContext(); + CamelContextAware.trySetCamelContext(tst, context); + tst.init(context); + return context; + } + + @Test + void testRouteMultipleRequests() throws InterruptedException, IOException { + int j = 10; + MockEndpoint mock = getMockEndpoint("mock:end"); + mock.expectedMessageCount(j); + mock.setAssertPeriod(5000); + for (int i = 0; i < j; i++) { + context.createProducerTemplate().sendBody("direct:start", "Hello!"); + } + mock.assertIsSatisfied(1000); + Map<String, OtelTrace> traces = otelExtension.getTraces(); + // Each trace should have a unique trace id. It is enough to assert that + // the number of elements in the map is the same of the requests to prove + // all traces have been generated uniquely. + assertEquals(j, traces.size()); + // Each trace should have the same structure + for (OtelTrace trace : traces.values()) { + checkTrace(trace, "Hello!"); + } + + } + + private void checkTrace(OtelTrace trace, String expectedBody) { + List<SpanData> spans = trace.getSpans(); + assertEquals(7, spans.size()); + SpanData testProducer = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://start", Op.EVENT_SENT); + SpanData direct = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://start", Op.EVENT_RECEIVED); + SpanData wiretapDirectTo = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://tap", Op.EVENT_SENT); + SpanData wiretapDirectFrom = OpenTelemetryTracerTestSupport.getSpan(spans, "direct://tap", Op.EVENT_RECEIVED); + SpanData log = OpenTelemetryTracerTestSupport.getSpan(spans, "log://info", Op.EVENT_SENT); + SpanData wiretapLog = OpenTelemetryTracerTestSupport.getSpan(spans, "log://tapped", Op.EVENT_SENT); + SpanData wiretapMock = OpenTelemetryTracerTestSupport.getSpan(spans, "mock://end", Op.EVENT_SENT); + + // Validate span completion + assertTrue(testProducer.hasEnded()); + assertTrue(direct.hasEnded()); + assertTrue(wiretapDirectTo.hasEnded()); + assertTrue(wiretapDirectFrom.hasEnded()); + assertTrue(log.hasEnded()); + assertTrue(wiretapLog.hasEnded()); + assertTrue(wiretapMock.hasEnded()); + + // Validate same trace + assertEquals(testProducer.getSpanContext().getTraceId(), direct.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), wiretapDirectTo.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), wiretapDirectFrom.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), log.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), wiretapLog.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), wiretapMock.getSpanContext().getTraceId()); + + // Validate different Exchange ID + assertNotEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + wiretapDirectTo.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + direct.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(testProducer.getAttributes().get(AttributeKey.stringKey("exchangeId")), + log.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(wiretapDirectTo.getAttributes().get(AttributeKey.stringKey("exchangeId")), + wiretapDirectFrom.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(wiretapDirectTo.getAttributes().get(AttributeKey.stringKey("exchangeId")), + wiretapLog.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + assertEquals(wiretapDirectTo.getAttributes().get(AttributeKey.stringKey("exchangeId")), + wiretapMock.getAttributes().get(AttributeKey.stringKey("exchangeId"))); + + // Validate hierarchy + assertFalse(testProducer.getParentSpanContext().isValid()); + assertEquals(testProducer.getSpanContext().getSpanId(), direct.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), log.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), wiretapDirectTo.getParentSpanContext().getSpanId()); + assertEquals(wiretapDirectTo.getSpanContext().getSpanId(), wiretapDirectFrom.getParentSpanContext().getSpanId()); + assertEquals(wiretapDirectFrom.getSpanContext().getSpanId(), wiretapLog.getParentSpanContext().getSpanId()); + assertEquals(wiretapDirectFrom.getSpanContext().getSpanId(), wiretapMock.getParentSpanContext().getSpanId()); + + // Validate message logging + assertEquals("A direct message", direct.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + assertEquals("A tapped message", wiretapDirectFrom.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + if (expectedBody == null) { + assertEquals( + "Exchange[ExchangePattern: InOut, BodyType: null, Body: [Body is null]]", + log.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + assertEquals( + "Exchange[ExchangePattern: InOut, BodyType: null, Body: [Body is null]]", + wiretapLog.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + } else { + assertEquals( + "Exchange[ExchangePattern: InOnly, BodyType: String, Body: " + expectedBody + "]", + log.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + assertEquals( + "Exchange[ExchangePattern: InOnly, BodyType: String, Body: " + expectedBody + "]", + wiretapLog.getEvents().get(0).getAttributes().get( + AttributeKey.stringKey("message"))); + } + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("start") + .wireTap("direct:tap") + .log("A direct message") + .to("log:info"); + + from("direct:tap") + .delay(2000) + .routeId("wiretapped") + .log("A tapped message") + .to("log:tapped") + .to("mock:end"); + } + }; + } +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/CamelOpenTelemetryExtension.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/CamelOpenTelemetryExtension.java new file mode 100644 index 00000000000..544018fa27f --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/CamelOpenTelemetryExtension.java @@ -0,0 +1,248 @@ +/* + * 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.opentelemetry2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender; +import io.opentelemetry.sdk.OpenTelemetrySdk; +//import io.opentelemetry.sdk.extension.incubator.trace.LeakDetectingSpanProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; + +/** + * Adapted from + * https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/junit5/OpenTelemetryExtension.java + */ +final class CamelOpenTelemetryExtension implements BeforeEachCallback, AfterEachCallback { + + /** + * Returns an extension with a default SDK initialized with an in-memory span exporter and W3C trace context + * propagation. + */ + static CamelOpenTelemetryExtension create() { + InMemorySpanExporter spanExporter = InMemorySpanExporter.create(); + SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + //.addSpanProcessor(LeakDetectingSpanProcessor.create()) + .addSpanProcessor(new LoggingSpanProcessor()) + .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) + .build(); + + InMemoryMetricReader metricReader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + + InMemoryLogRecordExporter logRecordExporter = InMemoryLogRecordExporter.create(); + SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder() + .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) + .build(); + + OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder() + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .setTracerProvider(tracerProvider) + .setMeterProvider(meterProvider) + .setLoggerProvider(loggerProvider) + .build(); + + return new CamelOpenTelemetryExtension(openTelemetry, spanExporter, metricReader, logRecordExporter); + } + + private final OpenTelemetrySdk openTelemetry; + private final InMemorySpanExporter spanExporter; + private final InMemoryMetricReader metricReader; + private final InMemoryLogRecordExporter logRecordExporter; + + private CamelOpenTelemetryExtension(OpenTelemetrySdk openTelemetry, InMemorySpanExporter spanExporter, + InMemoryMetricReader metricReader, InMemoryLogRecordExporter logRecordExporter) { + this.openTelemetry = openTelemetry; + this.spanExporter = spanExporter; + this.metricReader = metricReader; + this.logRecordExporter = logRecordExporter; + } + + /** + * Returns the {@link OpenTelemetrySdk} created by this extension. + */ + public OpenTelemetry getOpenTelemetry() { + return openTelemetry; + } + + /** + * Returns all the exported {@link SpanData} so far. + */ + public List<SpanData> getSpans() { + return spanExporter.getFinishedSpanItems(); + } + + /** + * Returns the current {@link MetricData} in {@link AggregationTemporality#CUMULATIVE} format. + */ + public List<MetricData> getMetrics() { + return new ArrayList<>(metricReader.collectAllMetrics()); + } + + /** + * Returns all the exported {@link LogRecordData} so far. + */ + public List<LogRecordData> getLogRecords() { + return new ArrayList<>(logRecordExporter.getFinishedLogRecordItems()); + } + + /** + * Clears the collected exported {@link SpanData}. Consider making your test smaller instead of manually clearing + * state using this method. + */ + public void clearSpans() { + spanExporter.reset(); + } + + /** + * Clears all registered metric instruments, such that {@link #getMetrics()} is empty. + */ + public void clearMetrics() { + SdkMeterProviderUtil.resetForTest(openTelemetry.getSdkMeterProvider()); + } + + /** + * Clears the collected exported {@link LogRecordData}. Consider making your test smaller instead of manually + * clearing state using this method. + */ + public void clearLogRecords() { + logRecordExporter.reset(); + } + + @Override + public void beforeEach(ExtensionContext context) { + GlobalOpenTelemetry.resetForTest(); + GlobalOpenTelemetry.set(openTelemetry); + OpenTelemetryAppender.install(openTelemetry); + } + + @Override + public void afterEach(ExtensionContext context) { + GlobalOpenTelemetry.resetForTest(); + openTelemetry.close(); + } + + static class LoggingSpanProcessor implements SpanProcessor { + private static final Logger LOG = LoggerFactory.getLogger(LoggingSpanProcessor.class); + private static final Marker OTEL_MARKER = MarkerFactory.getMarker("OTEL"); + + @Override + public void onStart(Context context, ReadWriteSpan readWriteSpan) { + LOG.info(OTEL_MARKER, "Span started: name - '{}', kind - '{}', id - '{}-{}", readWriteSpan.getName(), + readWriteSpan.getKind(), + readWriteSpan.getSpanContext().getTraceId(), readWriteSpan.getSpanContext().getSpanId()); + } + + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void onEnd(ReadableSpan readableSpan) { + LOG.info(OTEL_MARKER, "Span ended: name - '{}', kind - '{}', id - '{}-{}", readableSpan.getName(), + readableSpan.getKind(), + readableSpan.getSpanContext().getTraceId(), readableSpan.getSpanContext().getSpanId()); + } + + @Override + public boolean isEndRequired() { + return true; + } + } + + Map<String, OtelTrace> getTraces() { + Map<String, OtelTrace> answer = new HashMap<>(); + for (SpanData span : this.getSpans()) { + String traceId = span.getTraceId(); + OtelTrace trace = answer.get(traceId); + if (trace == null) { + trace = new OtelTrace(traceId); + answer.put(traceId, trace); + } + trace.addSpan(span); + } + + // Sort the spans for all traces + answer.forEach((id, trace) -> Collections.sort(trace.getSpans(), new SpanComparator())); + + return answer; + } + + class OtelTrace { + String traceId; + List<SpanData> spans; + + OtelTrace(String traceId) { + this.traceId = traceId; + this.spans = new ArrayList<>(); + } + + void addSpan(SpanData span) { + this.spans.add(span); + } + + List<SpanData> getSpans() { + return this.spans; + } + + @Override + public String toString() { + return traceId + " " + spans; + } + } + + class SpanComparator implements java.util.Comparator<SpanData> { + @Override + public int compare(SpanData a, SpanData b) { + Long nanosA = a.getStartEpochNanos(); + Long nanosB = b.getStartEpochNanos(); + return (int) (nanosA - nanosB); + } + } +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/DisableEndpointTest.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/DisableEndpointTest.java new file mode 100644 index 00000000000..a8a805cde43 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/DisableEndpointTest.java @@ -0,0 +1,89 @@ +/* + * 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.opentelemetry2; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DisableEndpointTest extends OpenTelemetryTracerTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + OpenTelemetryTracer tst = new OpenTelemetryTracer(); + tst.setTraceProcessors(true); + tst.setExcludePatterns("log*,to*"); + tst.setTracer(otelExtension.getOpenTelemetry().getTracer("traceTest")); + tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators()); + CamelContext context = super.createCamelContext(); + CamelContextAware.trySetCamelContext(tst, context); + tst.init(context); + return context; + } + + @Test + void testProcessorsTraceRequest() throws IOException { + template.sendBody("direct:start", "my-body"); + Map<String, OtelTrace> traces = otelExtension.getTraces(); + assertEquals(1, traces.size()); + checkTrace(traces.values().iterator().next()); + } + + private void checkTrace(OtelTrace trace) { + List<SpanData> spans = trace.getSpans(); + assertEquals(2, spans.size()); + SpanData testProducer = spans.get(0); + SpanData direct = spans.get(1); + + // Validate span completion + assertTrue(testProducer.hasEnded()); + assertTrue(direct.hasEnded()); + + // Validate same trace + assertEquals(testProducer.getSpanContext().getTraceId(), direct.getSpanContext().getTraceId()); + + // Validate hierarchy + assertFalse(testProducer.getParentSpanContext().isValid()); + assertEquals(testProducer.getSpanContext().getSpanId(), direct.getParentSpanContext().getSpanId()); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("start") + .log("A message") + .to("log:info"); + } + }; + } + +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/EnableProcessorsTest.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/EnableProcessorsTest.java new file mode 100644 index 00000000000..76f4abe6868 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/EnableProcessorsTest.java @@ -0,0 +1,118 @@ +/* + * 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.opentelemetry2; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace; +import org.apache.camel.telemetry.Op; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EnableProcessorsTest extends OpenTelemetryTracerTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + OpenTelemetryTracer tst = new OpenTelemetryTracer(); + tst.setTraceProcessors(true); + tst.setTracer(otelExtension.getOpenTelemetry().getTracer("traceTest")); + tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators()); + CamelContext context = super.createCamelContext(); + CamelContextAware.trySetCamelContext(tst, context); + tst.init(context); + return context; + } + + @Test + void testProcessorsTraceRequest() throws IOException { + template.sendBody("direct:start", "my-body"); + Map<String, OtelTrace> traces = otelExtension.getTraces(); + assertEquals(1, traces.size()); + checkTrace(traces.values().iterator().next()); + } + + private void checkTrace(OtelTrace trace) { + List<SpanData> spans = trace.getSpans(); + assertEquals(6, spans.size()); + SpanData testProducer = spans.get(0); + SpanData direct = spans.get(1); + SpanData innerLog = spans.get(2); + SpanData innerProcessor = spans.get(3); + SpanData log = spans.get(4); + SpanData innerToLog = spans.get(5); + + // Validate span completion + assertTrue(testProducer.hasEnded()); + assertTrue(direct.hasEnded()); + assertTrue(innerLog.hasEnded()); + assertTrue(innerProcessor.hasEnded()); + assertTrue(log.hasEnded()); + assertTrue(innerToLog.hasEnded()); + + // Validate same trace + assertEquals(testProducer.getSpanContext().getTraceId(), direct.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), innerLog.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), innerProcessor.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), log.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), innerToLog.getSpanContext().getTraceId()); + + // Validate operations + assertEquals(Op.EVENT_RECEIVED.toString(), direct.getAttributes().get(AttributeKey.stringKey("op"))); + assertEquals(Op.EVENT_PROCESS.toString(), innerProcessor.getAttributes().get(AttributeKey.stringKey("op"))); + + // Validate hierarchy + assertFalse(testProducer.getParentSpanContext().isValid()); + assertEquals(testProducer.getSpanContext().getSpanId(), direct.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), innerLog.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), innerProcessor.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), log.getParentSpanContext().getSpanId()); + assertEquals(log.getSpanContext().getSpanId(), innerToLog.getParentSpanContext().getSpanId()); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("start") + .log("A message") + .process(new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + exchange.getIn().setHeader("operation", "fake"); + } + }) + .to("log:info"); + } + }; + } + +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerTest.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerTest.java new file mode 100644 index 00000000000..3b4118656f6 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerTest.java @@ -0,0 +1,131 @@ +/* + * 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.opentelemetry2; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.Exchange; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace; +import org.apache.camel.telemetry.Op; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OpenTelemetryTracerTest extends OpenTelemetryTracerTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + OpenTelemetryTracer tst = new OpenTelemetryTracer(); + tst.setTracer(otelExtension.getOpenTelemetry().getTracer("traceTest")); + tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators()); + CamelContext context = super.createCamelContext(); + CamelContextAware.trySetCamelContext(tst, context); + tst.init(context); + return context; + } + + @Test + void testRouteSingleRequest() throws IOException { + Exchange result = template.request("direct:start", null); + // Make sure the trace is propagated downstream + assertNotNull(result.getIn().getHeader("traceparent")); + Map<String, OtelTrace> traces = otelExtension.getTraces(); + assertEquals(1, traces.size()); + checkTrace(traces.values().iterator().next(), null); + } + + @Test + void testRouteMultipleRequests() throws IOException { + for (int i = 1; i <= 10; i++) { + context.createProducerTemplate().sendBody("direct:start", "Hello!"); + } + Map<String, OtelTrace> traces = otelExtension.getTraces(); + // Each trace should have a unique trace id. It is enough to assert that + // the number of elements in the map is the same of the requests to prove + // all traces have been generated uniquely. + assertEquals(10, traces.size()); + // Each trace should have the same structure + for (OtelTrace trace : traces.values()) { + checkTrace(trace, "Hello!"); + } + + } + + private void checkTrace(OtelTrace trace, String expectedBody) { + List<SpanData> spans = trace.getSpans(); + assertEquals(3, spans.size()); + SpanData testProducer = spans.get(0); + SpanData direct = spans.get(1); + SpanData log = spans.get(2); + + // Validate span completion + assertTrue(testProducer.hasEnded()); + assertTrue(direct.hasEnded()); + assertTrue(log.hasEnded()); + + // Validate same trace + assertEquals(testProducer.getSpanContext().getTraceId(), direct.getSpanContext().getTraceId()); + assertEquals(direct.getSpanContext().getTraceId(), log.getSpanContext().getTraceId()); + + // Validate hierarchy + assertFalse(testProducer.getParentSpanContext().isValid()); + assertEquals(testProducer.getSpanContext().getSpanId(), direct.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), log.getParentSpanContext().getSpanId()); + + // Validate operations + assertEquals(Op.EVENT_SENT.toString(), testProducer.getAttributes().get(AttributeKey.stringKey("op"))); + assertEquals(Op.EVENT_RECEIVED.toString(), direct.getAttributes().get(AttributeKey.stringKey("op"))); + + // Validate message logging + assertEquals("A message", direct.getEvents().get(0).getAttributes().get(AttributeKey.stringKey("message"))); + if (expectedBody == null) { + assertEquals( + "Exchange[ExchangePattern: InOut, BodyType: null, Body: [Body is null]]", + log.getEvents().get(0).getAttributes().get(AttributeKey.stringKey("message"))); + } else { + assertEquals( + "Exchange[ExchangePattern: InOnly, BodyType: String, Body: " + expectedBody + "]", + log.getEvents().get(0).getAttributes().get(AttributeKey.stringKey("message"))); + } + + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("start") + .log("A message") + .to("log:info"); + } + }; + } + +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerTestSupport.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerTestSupport.java new file mode 100644 index 00000000000..416cedb5999 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerTestSupport.java @@ -0,0 +1,44 @@ +/* + * 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.opentelemetry2; + +import java.util.List; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.telemetry.Op; +import org.apache.camel.telemetry.TagConstants; +import org.apache.camel.test.junit5.ExchangeTestSupport; + +public class OpenTelemetryTracerTestSupport extends ExchangeTestSupport { + + protected CamelOpenTelemetryExtension otelExtension = CamelOpenTelemetryExtension.create(); + + protected static SpanData getSpan(List<SpanData> trace, String uri, Op op) { + for (SpanData span : trace) { + String camelURI = span.getAttributes().get(AttributeKey.stringKey("camel.uri")); + if (camelURI != null && camelURI.equals(uri)) { + String operation = span.getAttributes().get(AttributeKey.stringKey(TagConstants.OP)); + if (operation != null && operation.equals(op.toString())) { + return span; + } + } + } + throw new IllegalArgumentException("Trying to get a non existing span!"); + } + +} diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanPropagationTest.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanPropagationTest.java new file mode 100644 index 00000000000..8e45472bf65 --- /dev/null +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanPropagationTest.java @@ -0,0 +1,92 @@ +/* + * 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.opentelemetry2; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import io.opentelemetry.sdk.trace.data.SpanData; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SpanPropagationTest extends OpenTelemetryTracerTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + OpenTelemetryTracer tst = new OpenTelemetryTracer(); + tst.setTracer(otelExtension.getOpenTelemetry().getTracer("traceTest")); + tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators()); + CamelContext context = super.createCamelContext(); + CamelContextAware.trySetCamelContext(tst, context); + tst.init(context); + return context; + } + + @Test + void testPropagateUpstreamTraceRequest() throws IOException { + template.requestBodyAndHeader("direct:start", "sample body", + "traceparent", "00-0af044aea5c127fd5ab5f839de2b8ae2-d362a8a943c2b289-01"); + Map<String, OtelTrace> traces = otelExtension.getTraces(); + assertEquals(1, traces.size()); + checkTrace(traces.values().iterator().next()); + } + + private void checkTrace(OtelTrace trace) { + List<SpanData> spans = trace.getSpans(); + assertEquals(3, spans.size()); + SpanData testProducer = spans.get(0); + SpanData direct = spans.get(1); + SpanData log = spans.get(2); + + // Validate span completion + assertTrue(testProducer.hasEnded()); + assertTrue(direct.hasEnded()); + assertTrue(log.hasEnded()); + + // Validate same trace + assertEquals("0af044aea5c127fd5ab5f839de2b8ae2", testProducer.getSpanContext().getTraceId()); + assertEquals(testProducer.getSpanContext().getTraceId(), direct.getSpanContext().getTraceId()); + assertEquals(direct.getSpanContext().getTraceId(), log.getSpanContext().getTraceId()); + + // Validate hierarchy + assertEquals("d362a8a943c2b289", testProducer.getParentSpanContext().getSpanId()); + assertEquals(testProducer.getSpanContext().getSpanId(), direct.getParentSpanContext().getSpanId()); + assertEquals(direct.getSpanContext().getSpanId(), log.getParentSpanContext().getSpanId()); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("start") + .log("A message") + .to("log:info"); + } + }; + } + +} diff --git a/components/camel-opentelemetry2/src/test/resources/log4j2.properties b/components/camel-opentelemetry2/src/test/resources/log4j2.properties new file mode 100644 index 00000000000..c75cd8e27dd --- /dev/null +++ b/components/camel-opentelemetry2/src/test/resources/log4j2.properties @@ -0,0 +1,45 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +appender.file.type=File +appender.file.name=file +appender.file.fileName=target/camel-opentelemetry-test.log +appender.file.layout.type=PatternLayout +appender.file.layout.pattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n + +appender.out.type=Console +appender.out.name=out +appender.out.layout.type=PatternLayout +appender.out.layout.pattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n + +# The CamelOpenTelemetryExtension relies on logs with the marker "OTEL" to identify which logs were actually sent +# to trace. +# If the rootLogger level is changed, the logging calls on `CamelOpenTelemetryExtension.LoggingSpanProcessor` +# may need to be adjusted. +appender.opentelemetry.type=OpenTelemetry +appender.opentelemetry.name=opentelemetry +appender.opentelemetry.filter.marker.type=MarkerFilter +appender.opentelemetry.filter.marker.onMatch=ACCEPT +appender.opentelemetry.filter.marker.onMismatch=DENY +appender.opentelemetry.filter.marker.marker=OTEL +appender.opentelemetry.layout.type=PatternLayout +appender.opentelemetry.layout.pattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n + +rootLogger.level=INFO +rootLogger.appenderRefs=file,out,opentelemetry +rootLogger.appenderRef.file.ref=file +#rootLogger.appenderRef.out.ref=out +rootLogger.appenderRef.opentelemetry.ref=opentelemetry diff --git a/components/pom.xml b/components/pom.xml index cd617f5b62a..ad1a2ec9954 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -235,6 +235,7 @@ <module>camel-olingo4</module> <module>camel-openstack</module> <module>camel-opentelemetry</module> + <module>camel-opentelemetry2</module> <module>camel-paho</module> <module>camel-paho-mqtt5</module> <module>camel-parquet-avro</module> diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/Otel2ConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/Otel2ConfigurationPropertiesConfigurer.java new file mode 100644 index 00000000000..4fd58303ebc --- /dev/null +++ b/core/camel-main/src/generated/java/org/apache/camel/main/Otel2ConfigurationPropertiesConfigurer.java @@ -0,0 +1,85 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.main; + +import javax.annotation.processing.Generated; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.ExtendedPropertyConfigurerGetter; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.spi.ConfigurerStrategy; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.main.Otel2ConfigurationProperties; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo") +@SuppressWarnings("unchecked") +public class Otel2ConfigurationPropertiesConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, ExtendedPropertyConfigurerGetter { + + private static final Map<String, Object> ALL_OPTIONS; + static { + Map<String, Object> map = new CaseInsensitiveMap(); + map.put("Enabled", boolean.class); + map.put("Encoding", boolean.class); + map.put("ExcludePatterns", java.lang.String.class); + map.put("InstrumentationName", java.lang.String.class); + map.put("TraceProcessors", boolean.class); + ALL_OPTIONS = map; + } + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + org.apache.camel.main.Otel2ConfigurationProperties target = (org.apache.camel.main.Otel2ConfigurationProperties) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "enabled": target.setEnabled(property(camelContext, boolean.class, value)); return true; + case "encoding": target.setEncoding(property(camelContext, boolean.class, value)); return true; + case "excludepatterns": + case "excludePatterns": target.setExcludePatterns(property(camelContext, java.lang.String.class, value)); return true; + case "instrumentationname": + case "instrumentationName": target.setInstrumentationName(property(camelContext, java.lang.String.class, value)); return true; + case "traceprocessors": + case "traceProcessors": target.setTraceProcessors(property(camelContext, boolean.class, value)); return true; + default: return false; + } + } + + @Override + public Map<String, Object> getAllOptions(Object target) { + return ALL_OPTIONS; + } + + @Override + public Class<?> getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "enabled": return boolean.class; + case "encoding": return boolean.class; + case "excludepatterns": + case "excludePatterns": return java.lang.String.class; + case "instrumentationname": + case "instrumentationName": return java.lang.String.class; + case "traceprocessors": + case "traceProcessors": return boolean.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + org.apache.camel.main.Otel2ConfigurationProperties target = (org.apache.camel.main.Otel2ConfigurationProperties) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "enabled": return target.isEnabled(); + case "encoding": return target.isEncoding(); + case "excludepatterns": + case "excludePatterns": return target.getExcludePatterns(); + case "instrumentationname": + case "instrumentationName": return target.getInstrumentationName(); + case "traceprocessors": + case "traceProcessors": return target.isTraceProcessors(); + default: return null; + } + } +} + diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index 0c820eb09e6..626329f7b43 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -17,6 +17,7 @@ { "name": "camel.vault.kubernetescm", "description": "Camel Kubernetes Configmaps Vault configurations", "sourceType": "org.apache.camel.vault.KubernetesConfigMapVaultConfiguration" }, { "name": "camel.vault.hashicorp", "description": "Camel Hashicorp Vault configurations", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration" }, { "name": "camel.opentelemetry", "description": "Camel OpenTelemetry configurations", "sourceType": "org.apache.camel.main.OtelConfigurationProperties" }, + { "name": "camel.opentelemetry2", "description": "Camel OpenTelemetry 2 configurations", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties" }, { "name": "camel.telemetryDev", "description": "Camel Telemetry Dev configurations", "sourceType": "org.apache.camel.main.TelemetryDevConfigurationProperties" }, { "name": "camel.metrics", "description": "Camel Micrometer Metrics configurations", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties" }, { "name": "camel.faulttolerance", "description": "Fault Tolerance EIP Circuit Breaker configurations", "sourceType": "org.apache.camel.main.FaultToleranceConfigurationProperties" }, @@ -205,6 +206,11 @@ { "name": "camel.opentelemetry.excludePatterns", "description": "Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns can be separated by comma.", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, { "name": "camel.opentelemetry.instrumentationName", "description": "A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully qualified class name. Must not be null.", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "camel" }, { "name": "camel.opentelemetry.traceProcessors", "description": "Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern property to filter out Processors.", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.opentelemetry2.enabled", "description": "To enable OpenTelemetry 2", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.opentelemetry2.encoding", "description": "Sets whether the header keys need to be encoded (connector specific) or not. The value is a boolean. Dashes need for instances to be encoded for JMS property keys.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, + { "name": "camel.opentelemetry2.excludePatterns", "description": "Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns can be separated by comma.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, + { "name": "camel.opentelemetry2.instrumentationName", "description": "A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully qualified class name. Must not be null.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "camel" }, + { "name": "camel.opentelemetry2.traceProcessors", "description": "Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern property to filter out Processors.", "sourceType": "org.apache.camel.main.Otel2ConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" }, { "name": "camel.resilience4j.automaticTransitionFromOpenToHalfOpenEnabled", "description": "Enables automatic transition from OPEN to HALF_OPEN state once the waitDurationInOpenState has passed.", "sourceType": "org.apache.camel.main.Resilience4jConfigurationProperties", "type": "boolean", "javaType": "java.lang.Boolean", "defaultValue": "false" }, { "name": "camel.resilience4j.bulkheadEnabled", "description": "Whether bulkhead is enabled or not on the circuit breaker.", "sourceType": "org.apache.camel.main.Resilience4jConfigurationProperties", "type": "boolean", "javaType": "java.lang.Boolean", "defaultValue": false }, { "name": "camel.resilience4j.bulkheadMaxConcurrentCalls", "description": "Configures the max amount of concurrent calls the bulkhead will support.", "sourceType": "org.apache.camel.main.Resilience4jConfigurationProperties", "type": "integer", "javaType": "java.lang.Integer" }, diff --git a/core/camel-main/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.main.Otel2ConfigurationProperties b/core/camel-main/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.main.Otel2ConfigurationProperties new file mode 100644 index 00000000000..1081406fa21 --- /dev/null +++ b/core/camel-main/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.main.Otel2ConfigurationProperties @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.main.Otel2ConfigurationPropertiesConfigurer diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 6f8138ba3cb..e41711d5f19 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -468,7 +468,7 @@ The camel.vault.hashicorp supports 6 options, which are listed below. === Camel OpenTelemetry configurations -The camel.opentelemetry supports 5 options, which are listed below. +The camel.opentelemetry supports 10 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -478,6 +478,25 @@ The camel.opentelemetry supports 5 options, which are listed below. | *camel.opentelemetry.exclude{zwsp}Patterns* | Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns can be separated by comma. | | String | *camel.opentelemetry.instrumentation{zwsp}Name* | A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully qualified class name. Must not be null. | camel | String | *camel.opentelemetry.trace{zwsp}Processors* | Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern property to filter out Processors. | false | boolean +| *camel.opentelemetry2.enabled* | To enable OpenTelemetry 2 | false | boolean +| *camel.opentelemetry2.encoding* | Sets whether the header keys need to be encoded (connector specific) or not. The value is a boolean. Dashes need for instances to be encoded for JMS property keys. | false | boolean +| *camel.opentelemetry2.exclude{zwsp}Patterns* | Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns can be separated by comma. | | String +| *camel.opentelemetry2.instrumentation{zwsp}Name* | A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully qualified class name. Must not be null. | camel | String +| *camel.opentelemetry2.trace{zwsp}Processors* | Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern property to filter out Processors. | false | boolean +|=== + + +=== Camel OpenTelemetry 2 configurations +The camel.opentelemetry2 supports 5 options, which are listed below. + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *camel.opentelemetry2.enabled* | To enable OpenTelemetry 2 | false | boolean +| *camel.opentelemetry2.encoding* | Sets whether the header keys need to be encoded (connector specific) or not. The value is a boolean. Dashes need for instances to be encoded for JMS property keys. | false | boolean +| *camel.opentelemetry2.exclude{zwsp}Patterns* | Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns can be separated by comma. | | String +| *camel.opentelemetry2.instrumentation{zwsp}Name* | A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully qualified class name. Must not be null. | camel | String +| *camel.opentelemetry2.trace{zwsp}Processors* | Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern property to filter out Processors. | false | boolean |=== diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index c409ca4f2cc..31580cf06fa 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -135,7 +135,8 @@ public abstract class BaseMainSupport extends BaseService { private static final String[] GROUP_PREFIXES = new String[] { "camel.context.", "camel.resilience4j.", "camel.faulttolerance.", "camel.rest.", "camel.vault.", "camel.threadpool.", "camel.health.", - "camel.lra.", "camel.opentelemetry.", "camel.telemetryDev.", "camel.metrics.", "camel.routeTemplate", + "camel.lra.", "camel.opentelemetry2.", "camel.opentelemetry.", + "camel.telemetryDev.", "camel.metrics.", "camel.routeTemplate", "camel.devConsole.", "camel.variable.", "camel.beans.", "camel.globalOptions.", "camel.server.", "camel.ssl.", "camel.debug.", "camel.trace.", "camel.routeController." }; @@ -1244,6 +1245,7 @@ public abstract class BaseMainSupport extends BaseService { OrderedLocationProperties healthProperties = new OrderedLocationProperties(); OrderedLocationProperties lraProperties = new OrderedLocationProperties(); OrderedLocationProperties otelProperties = new OrderedLocationProperties(); + OrderedLocationProperties otel2Properties = new OrderedLocationProperties(); OrderedLocationProperties telemetryDevProperties = new OrderedLocationProperties(); OrderedLocationProperties metricsProperties = new OrderedLocationProperties(); OrderedLocationProperties routeTemplateProperties = new OrderedLocationProperties(); @@ -1301,6 +1303,12 @@ public abstract class BaseMainSupport extends BaseService { String option = key.substring(10); validateOptionAndValue(key, option, value); lraProperties.put(loc, optionKey(option), value); + } else if (startsWithIgnoreCase(key, "camel.opentelemetry2.")) { + // grab the value + String value = prop.getProperty(key); + String option = key.substring(21); + validateOptionAndValue(key, option, value); + otel2Properties.put(loc, optionKey(option), value); } else if (startsWithIgnoreCase(key, "camel.opentelemetry.")) { // grab the value String value = prop.getProperty(key); @@ -1448,6 +1456,10 @@ public abstract class BaseMainSupport extends BaseService { LOG.debug("Auto-configuring OpenTelemetry from loaded properties: {}", otelProperties.size()); setOtelProperties(camelContext, otelProperties, mainConfigurationProperties.isAutoConfigurationFailFast(), autoConfiguredProperties); + } else if (!otel2Properties.isEmpty() || mainConfigurationProperties.hasOtel2Configuration()) { + LOG.debug("Auto-configuring OpenTelemetry 2 from loaded properties: {}", otel2Properties.size()); + setOtel2Properties(camelContext, otel2Properties, mainConfigurationProperties.isAutoConfigurationFailFast(), + autoConfiguredProperties); } else { // Attempt to fallback to Telemetry simple only if no other tracing service is found if (!telemetryDevProperties.isEmpty() || mainConfigurationProperties.hasTelemetryDevConfiguration()) { @@ -1573,6 +1585,11 @@ public abstract class BaseMainSupport extends BaseService { LOG.warn("Property not auto-configured: camel.opentelemetry.{}={}", k, v); }); } + if (!otel2Properties.isEmpty()) { + otel2Properties.forEach((k, v) -> { + LOG.warn("Property not auto-configured: camel.opentelemetry2.{}={}", k, v); + }); + } if (!telemetryDevProperties.isEmpty()) { telemetryDevProperties.forEach((k, v) -> { LOG.warn("Property not auto-configured: camel.telemetryDev.{}={}", k, v); @@ -1756,6 +1773,28 @@ public abstract class BaseMainSupport extends BaseService { } } + private void setOtel2Properties( + CamelContext camelContext, OrderedLocationProperties otel2Properties, + boolean failIfNotSet, OrderedLocationProperties autoConfiguredProperties) + throws Exception { + + String loc = otel2Properties.getLocation("enabled"); + Object obj = otel2Properties.remove("enabled"); + if (ObjectHelper.isNotEmpty(obj)) { + autoConfiguredProperties.put(loc, "camel.opentelemetry2.enabled", obj.toString()); + } + boolean enabled = obj != null ? CamelContextHelper.parseBoolean(camelContext, obj.toString()) : true; + if (enabled) { + CamelTracingService otel = resolveOtel2Service(camelContext); + setPropertiesOnTarget(camelContext, otel, otel2Properties, "camel.opentelemetry2.", failIfNotSet, true, + autoConfiguredProperties); + if (camelContext.hasService(CamelTracingService.class) == null) { + // add as service so tracing can be active + camelContext.addService(otel, true, true); + } + } + } + private void setTelemetryDevProperties( CamelContext camelContext, OrderedLocationProperties telemetryDevProperties, boolean failIfNotSet, OrderedLocationProperties autoConfiguredProperties) @@ -2623,6 +2662,20 @@ public abstract class BaseMainSupport extends BaseService { return answer; } + private static CamelTracingService resolveOtel2Service(CamelContext camelContext) throws Exception { + CamelTracingService answer = camelContext.hasService(CamelTracingService.class); + if (answer == null) { + answer = camelContext.getRegistry().findSingleByType(CamelTracingService.class); + } + if (answer == null) { + answer = camelContext.getCamelContextExtension().getBootstrapFactoryFinder() + .newInstance("opentelemetry-tracer-2", CamelTracingService.class) + .orElseThrow(() -> new IllegalArgumentException( + "Cannot find OpenTelemetryTracer2 on classpath. Add camel-opentelemetry-2 to classpath.")); + } + return answer; + } + private static CamelTracingService resolveTelemetryDevService(CamelContext camelContext) throws Exception { CamelTracingService answer = camelContext.hasService(CamelTracingService.class); if (answer == null) { diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java index a83912e9060..29de01e0fc2 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java @@ -60,6 +60,7 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< private StartupConditionConfigurationProperties startupConditionConfigurationProperties; private LraConfigurationProperties lraConfigurationProperties; private OtelConfigurationProperties otelConfigurationProperties; + private Otel2ConfigurationProperties otel2ConfigurationProperties; private TelemetryDevConfigurationProperties telemetryDevConfigurationProperties; private MetricsConfigurationProperties metricsConfigurationProperties; private ThreadPoolConfigurationProperties threadPoolProperties; @@ -87,6 +88,14 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< otelConfigurationProperties.close(); otelConfigurationProperties = null; } + if (otel2ConfigurationProperties != null) { + otel2ConfigurationProperties.close(); + otel2ConfigurationProperties = null; + } + if (telemetryDevConfigurationProperties != null) { + telemetryDevConfigurationProperties.close(); + telemetryDevConfigurationProperties = null; + } if (metricsConfigurationProperties != null) { metricsConfigurationProperties.close(); metricsConfigurationProperties = null; @@ -209,6 +218,13 @@ public class MainConfigurationProperties extends DefaultConfigurationProperties< return otelConfigurationProperties != null; } + /** + * Whether there has been any OpenTelemetry configuration specified + */ + public boolean hasOtel2Configuration() { + return otel2ConfigurationProperties != null; + } + /** * Whether there has been any TelemetryDev configuration specified */ diff --git a/core/camel-main/src/main/java/org/apache/camel/main/Otel2ConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/Otel2ConfigurationProperties.java new file mode 100644 index 00000000000..248051521af --- /dev/null +++ b/core/camel-main/src/main/java/org/apache/camel/main/Otel2ConfigurationProperties.java @@ -0,0 +1,154 @@ +/* + * 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.main; + +import org.apache.camel.spi.BootstrapCloseable; +import org.apache.camel.spi.Configurer; +import org.apache.camel.spi.Metadata; + +/** + * Global configuration for OpenTelemetry 2 (based on camel-telemetry) + */ +@Configurer(extended = true) +public class Otel2ConfigurationProperties implements BootstrapCloseable { + + private MainConfigurationProperties parent; + + private boolean enabled; + @Metadata(defaultValue = "camel", required = true) + private String instrumentationName = "camel"; + private boolean encoding; + private String excludePatterns; + private boolean traceProcessors; + + public Otel2ConfigurationProperties(MainConfigurationProperties parent) { + this.parent = parent; + } + + public MainConfigurationProperties end() { + return parent; + } + + @Override + public void close() { + parent = null; + } + + public boolean isEnabled() { + return enabled; + } + + /** + * To enable OpenTelemetry 2 + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getInstrumentationName() { + return instrumentationName; + } + + /** + * A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully + * qualified class name. Must not be null. + */ + public void setInstrumentationName(String instrumentationName) { + this.instrumentationName = instrumentationName; + } + + public boolean isEncoding() { + return encoding; + } + + /** + * Sets whether the header keys need to be encoded (connector specific) or not. The value is a boolean. Dashes need + * for instances to be encoded for JMS property keys. + */ + public void setEncoding(boolean encoding) { + this.encoding = encoding; + } + + public String getExcludePatterns() { + return excludePatterns; + } + + /** + * Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns + * can be separated by comma. + */ + public void setExcludePatterns(String excludePatterns) { + this.excludePatterns = excludePatterns; + } + + public boolean isTraceProcessors() { + return traceProcessors; + } + + /** + * Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern + * property to filter out Processors. + */ + public void setTraceProcessors(boolean traceProcessors) { + this.traceProcessors = traceProcessors; + } + + /** + * A name uniquely identifying the instrumentation scope, such as the instrumentation library, package, or fully + * qualified class name. Must not be null. + */ + public Otel2ConfigurationProperties withInstrumentationName(String instrumentationName) { + this.instrumentationName = instrumentationName; + return this; + } + + /** + * To enable OpenTelemetry + */ + public Otel2ConfigurationProperties withEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + /** + * Sets whether the header keys need to be encoded (connector specific) or not. The value is a boolean. Dashes need + * for instances to be encoded for JMS property keys. + */ + public Otel2ConfigurationProperties withEncoding(boolean encoding) { + this.encoding = encoding; + return this; + } + + /** + * Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern. Multiple patterns + * can be separated by comma. + */ + public Otel2ConfigurationProperties withExcludePatterns(String excludePatterns) { + this.excludePatterns = excludePatterns; + return this; + } + + /** + * Setting this to true will create new OpenTelemetry Spans for each Camel Processors. Use the excludePattern + * property to filter out Processors. + */ + public Otel2ConfigurationProperties withTraceProcessors(boolean traceProcessors) { + this.traceProcessors = traceProcessors; + return this; + } + +} diff --git a/docs/components/modules/others/examples/json/opentelemetry2.json b/docs/components/modules/others/examples/json/opentelemetry2.json new file mode 120000 index 00000000000..f10f410b35c --- /dev/null +++ b/docs/components/modules/others/examples/json/opentelemetry2.json @@ -0,0 +1 @@ +../../../../../../components/camel-opentelemetry2/src/generated/resources/opentelemetry2.json \ No newline at end of file diff --git a/docs/components/modules/others/nav.adoc b/docs/components/modules/others/nav.adoc index 5c970bc5b4f..a50714416d4 100644 --- a/docs/components/modules/others/nav.adoc +++ b/docs/components/modules/others/nav.adoc @@ -36,6 +36,7 @@ ** xref:openapi-java.adoc[Openapi Java] ** xref:openapi-validator.adoc[Openapi Validator] ** xref:opentelemetry.adoc[OpenTelemetry] +** xref:opentelemetry2.adoc[Opentelemetry2] ** xref:platform-http-jolokia.adoc[Platform HTTP Jolokia] ** xref:platform-http-main.adoc[Platform Http Main] ** xref:platform-http-vertx.adoc[Platform Http Vertx] diff --git a/docs/components/modules/others/pages/opentelemetry2.adoc b/docs/components/modules/others/pages/opentelemetry2.adoc new file mode 120000 index 00000000000..04bcced0884 --- /dev/null +++ b/docs/components/modules/others/pages/opentelemetry2.adoc @@ -0,0 +1 @@ +../../../../../components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index 39354127791..145e208ff2e 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -2036,6 +2036,16 @@ <artifactId>camel-opentelemetry</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-opentelemetry-2</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-opentelemetry2</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-optaplanner</artifactId> diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelMainMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelMainMojo.java index c21082592c6..703695c1305 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelMainMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelMainMojo.java @@ -217,7 +217,9 @@ public class PrepareCamelMainMojo extends AbstractGeneratorMojo { prefix = "camel.startupcondition."; } else if (file.getName().contains("Lra")) { prefix = "camel.lra."; - } else if (file.getName().contains("Otel")) { + } else if (file.getName().contains("Otel2")) { + prefix = "camel.opentelemetry2."; + } else if (file.getName().contains("Otel") && !file.getName().contains("Otel2")) { prefix = "camel.opentelemetry."; } else if (file.getName().contains("TelemetryDev")) { prefix = "camel.telemetryDev."; @@ -395,6 +397,9 @@ public class PrepareCamelMainMojo extends AbstractGeneratorMojo { model.getGroups().add(new MainGroupModel( "camel.opentelemetry", "Camel OpenTelemetry configurations", "org.apache.camel.main.OtelConfigurationProperties")); + model.getGroups().add(new MainGroupModel( + "camel.opentelemetry2", "Camel OpenTelemetry 2 configurations", + "org.apache.camel.main.Otel2ConfigurationProperties")); model.getGroups().add(new MainGroupModel( "camel.telemetryDev", "Camel Telemetry Dev configurations", "org.apache.camel.main.TelemetryDevConfigurationProperties"));
