From 1b482ae890a73fdfcc8c4b116611f8c3e1b17229 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:19:57 +0100 Subject: [PATCH 1/9] remove obsolete things --- .../otel/common/ElasticAttributes.java | 7 --- custom/breakdown-metrics.md | 48 ------------------- .../smoketest/AgentFeaturesSmokeTest.java | 5 -- .../javaagent/smoketest/JavaExecutable.java | 10 ++-- 4 files changed, 4 insertions(+), 66 deletions(-) delete mode 100644 custom/breakdown-metrics.md diff --git a/common/src/main/java/co/elastic/otel/common/ElasticAttributes.java b/common/src/main/java/co/elastic/otel/common/ElasticAttributes.java index 5526d90e..e0ec7e93 100644 --- a/common/src/main/java/co/elastic/otel/common/ElasticAttributes.java +++ b/common/src/main/java/co/elastic/otel/common/ElasticAttributes.java @@ -22,13 +22,6 @@ import java.util.List; public interface ElasticAttributes { - AttributeKey SELF_TIME = AttributeKey.longKey("elastic.span.self_time"); - AttributeKey LOCAL_ROOT_ID = AttributeKey.stringKey("elastic.span.local_root.id"); - AttributeKey LOCAL_ROOT_NAME = AttributeKey.stringKey("elastic.local_root.name"); - AttributeKey LOCAL_ROOT_TYPE = AttributeKey.stringKey("elastic.local_root.type"); - AttributeKey IS_LOCAL_ROOT = AttributeKey.booleanKey("elastic.span.is_local_root"); - AttributeKey SPAN_TYPE = AttributeKey.stringKey("elastic.span.type"); - AttributeKey SPAN_SUBTYPE = AttributeKey.stringKey("elastic.span.subtype"); /** * Marker attribute for inferred spans. Does not have the elastic-prefix anymore because it has diff --git a/custom/breakdown-metrics.md b/custom/breakdown-metrics.md deleted file mode 100644 index ad56e67c..00000000 --- a/custom/breakdown-metrics.md +++ /dev/null @@ -1,48 +0,0 @@ -### Breakdown metrics - -Status: feature has been disabled and code is only kept for future reference. - -Breakdown metrics currently require a custom Elasticsearch ingest pipeline. - -``` -PUT _ingest/pipeline/metrics-apm.app@custom -{ - "processors": [ - { - "script": { - "lang": "painless", - "source": """ - -if(ctx.span == null){ - ctx.span = [:]; -} -if(ctx.transaction == null){ - ctx.transaction = [:]; -} -if(ctx.labels != null){ - if(ctx.labels.elastic_span_type != null){ - ctx.span.type = ctx.labels.elastic_span_type; - } - if(ctx.labels.elastic_span_subtype != null){ - ctx.span.subtype = ctx.labels.elastic_span_subtype; - } - if(ctx.labels.elastic_local_root_type != null){ - ctx.transaction.type = ctx.labels.elastic_local_root_type; - } - if(ctx.labels.elastic_local_root_name != null){ - ctx.transaction.name = ctx.labels.elastic_local_root_name; - } -} - -if(ctx.numeric_labels != null && ctx.numeric_labels.elastic_span_self_time != null){ - def value = ctx.numeric_labels.elastic_span_self_time/1000; - def sum = [ 'us': value]; - ctx.span.self_time = [ 'count': 0, 'sum': sum]; -} - - """ - } - } - ] -} -``` diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java index 96af1e10..1d0ef524 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java @@ -55,11 +55,6 @@ public void healthcheck() throws InterruptedException { spans.forEach( span -> { assertThat(getAttributes(span.getAttributesList())) - // span breakdown feature disabled - .doesNotContainKeys( - "elastic.span.self_time", - "elastic.span.is_local_root", - "elastic.span.local_root.id") .containsKeys("code.stacktrace"); }); } diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java index 39818517..5263007f 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java @@ -70,12 +70,10 @@ public static boolean isDebugInCI() { private static boolean probeListeningDebugger(int port) { // the most straightforward way to probe for an active debugger listening on port is to start - // another JVM - // with the debug options and check the process exit status. Trying to probe for open network - // port messes with - // the debugger and makes IDEA stop it. The only downside of this is that the debugger will - // first attach to this - // probe JVM, then the one running in a docker container we are aiming to debug. + // another JVM with the debug options and check the process exit status. Trying to probe for + // open network port messes with the debugger and makes IDEA stop it. The only downside of this + // is that the debugger will first attach to this probe JVM, then the one running in a docker + // container we are aiming to debug. try { Process process = new ProcessBuilder() From 7c934a78b74aa5c73891073d2312d84e06722105 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:14:16 +0100 Subject: [PATCH 2/9] rename features test to span stacktrace test --- .../{AgentFeaturesSmokeTest.java => SpanStackTraceTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename smoke-tests/src/test/java/com/example/javaagent/smoketest/{AgentFeaturesSmokeTest.java => SpanStackTraceTest.java} (97%) diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/SpanStackTraceTest.java similarity index 97% rename from smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java rename to smoke-tests/src/test/java/com/example/javaagent/smoketest/SpanStackTraceTest.java index 1d0ef524..6592d360 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/SpanStackTraceTest.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -class AgentFeaturesSmokeTest extends TestAppSmokeTest { +class SpanStackTraceTest extends TestAppSmokeTest { @BeforeAll public static void start() { From 883fba740abbc1d97ee939d64ad68c1bb2712c31 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:26:08 +0100 Subject: [PATCH 3/9] revert to a single class for common features --- .../smoketest/AgentFeaturesSmokeTest.java | 94 +++++++++++++++++++ .../smoketest/SpanStackTraceTest.java | 61 ------------ 2 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java delete mode 100644 smoke-tests/src/test/java/com/example/javaagent/smoketest/SpanStackTraceTest.java diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java new file mode 100644 index 00000000..1b4eb3a1 --- /dev/null +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 com.example.javaagent.smoketest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +import com.google.protobuf.Any; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.ArrayValue; +import io.opentelemetry.proto.trace.v1.Span; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class AgentFeaturesSmokeTest extends TestAppSmokeTest { + + @BeforeAll + public static void start() { + startTestApp( + (container) -> { + // capture span stacktrace for any duration + container.addEnv("ELASTIC_OTEL_JAVA_SPAN_STACKTRACE_MIN_DURATION", "0ms"); + // capture HTTP request/response headers on server side + // header key should not be case-sensitive in config + container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_REQUEST_HEADERS", "hello"); + container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_RESPONSE_HEADERS", "Content-length,Date"); + } + ); + } + + // TODO remove useless public modifiers ? + + @AfterAll + public static void end() { + stopApp(); + } + + @Test + public void spanCodeStackTrace() { + doRequest(getUrl("/health"), okResponseBody("Alive!")); + + List traces = waitForTraces(); + List spans = getSpans(traces).toList(); + assertThat(spans) + .hasSize(1) + .extracting("name", "kind") + .containsOnly(tuple("GET /health", Span.SpanKind.SPAN_KIND_SERVER)); + + spans.forEach( + span -> assertThat(getAttributes(span.getAttributesList())) + .containsKeys("code.stacktrace")); + } + + @Test + public void httpHeaderCapture() { + Map headers = new HashMap<>(); + headers.put("Hello", "World!"); + doRequest(getUrl("/health"), headers, okResponseBody("Alive!")); + + List traces = waitForTraces(); + List spans = getSpans(traces).toList(); + assertThat(spans) + .hasSize(1) + .extracting("name", "kind") + .containsOnly(tuple("GET /health", Span.SpanKind.SPAN_KIND_SERVER)); + + spans.forEach(span -> assertThat(getAttributes(span.getAttributesList())) + .containsEntry("http.request.header.hello", attributeArrayValue("World!")) + .containsEntry("http.response.header.content-length", attributeArrayValue("6")) + .containsKey("http.response.header.date")); + + } +} diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/SpanStackTraceTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/SpanStackTraceTest.java deleted file mode 100644 index 6592d360..00000000 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/SpanStackTraceTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. 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 com.example.javaagent.smoketest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; - -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; -import io.opentelemetry.proto.trace.v1.Span; -import java.util.List; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class SpanStackTraceTest extends TestAppSmokeTest { - - @BeforeAll - public static void start() { - startTestApp( - (container) -> container.addEnv("ELASTIC_OTEL_JAVA_SPAN_STACKTRACE_MIN_DURATION", "0ms")); - } - - @AfterAll - public static void end() { - stopApp(); - } - - @Test - public void healthcheck() throws InterruptedException { - doRequest(getUrl("/health"), okResponseBody("Alive!")); - - List traces = waitForTraces(); - List spans = getSpans(traces).toList(); - assertThat(spans) - .hasSize(1) - .extracting("name", "kind") - .containsOnly(tuple("GET /health", Span.SpanKind.SPAN_KIND_SERVER)); - - spans.forEach( - span -> { - assertThat(getAttributes(span.getAttributesList())) - .containsKeys("code.stacktrace"); - }); - } -} From e47121ac25073063f9b6232dbeed84547203aabd Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:26:36 +0100 Subject: [PATCH 4/9] fix minor issue with local debugging --- .../com/example/javaagent/smoketest/JavaExecutable.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java index 5263007f..be4f428c 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java @@ -78,12 +78,12 @@ private static boolean probeListeningDebugger(int port) { Process process = new ProcessBuilder() .command( - JavaExecutable.getBinaryPath().toString(), + JavaExecutable.getBinaryPath(), jvmDebugArgument("localhost", port), "-version") .start(); - process.waitFor(5, TimeUnit.SECONDS); - return process.exitValue() == 0; + boolean processExit = process.waitFor(5, TimeUnit.SECONDS); + return processExit && process.exitValue() == 0; } catch (InterruptedException | IOException e) { return false; } From 916f65605871e2dcb08d8143c5cfccb9d63e0cec Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:27:03 +0100 Subject: [PATCH 5/9] add ability to provide http headers + assert attribute array value --- .../javaagent/smoketest/SmokeTest.java | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java index c9e49804..14492572 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java @@ -26,6 +26,7 @@ import com.google.protobuf.util.JsonFormat; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.ArrayValue; import io.opentelemetry.proto.common.v1.KeyValue; import io.opentelemetry.proto.resource.v1.Resource; import io.opentelemetry.proto.trace.v1.ResourceSpans; @@ -34,6 +35,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -355,6 +357,14 @@ protected static AnyValue attributeValue(String value) { return AnyValue.newBuilder().setStringValue(value).build(); } + protected static AnyValue attributeArrayValue(String... values){ + ArrayValue.Builder valueBuilder = ArrayValue.newBuilder(); + for (String value : values) { + valueBuilder.addValues(AnyValue.newBuilder().setStringValue(value).build()); + } + return AnyValue.newBuilder().setArrayValue(valueBuilder.build()).build(); + } + protected static Map getAttributes(List list) { Map attributes = new HashMap<>(); for (KeyValue kv : list) { @@ -381,33 +391,37 @@ protected static String bytesToHex(byte[] bytes) { } return sb.toString(); } - protected void doRequest(String url, IOConsumer responseHandler) { - Request request = new Request.Builder().url(url).get().build(); + doRequest(url, Collections.emptyMap(), responseHandler); + } - try (Response response = client.newCall(request).execute()) { - responseHandler.accept(response); - } catch (IOException e) { - throw new RuntimeException(e); + protected void doRequest(String url, Map headers, IOConsumer responseHandler) { + Request.Builder request = new Request.Builder().url(url).get(); + headers.forEach(request::addHeader); + + try (Response response = client.newCall(request.build()).execute()) { + responseHandler.accept(response); + } catch (IOException e) { + throw new RuntimeException(e); + } } - } - @FunctionalInterface - protected interface IOConsumer { - void accept(T t) throws IOException; - } + @FunctionalInterface + protected interface IOConsumer { + void accept(T t) throws IOException; + } - protected static IOConsumer okResponse() { - return r -> { - assertThat(r.code()).isEqualTo(200); - }; - } + protected static IOConsumer okResponse() { + return r -> { + assertThat(r.code()).isEqualTo(200); + }; + } - protected static IOConsumer okResponseBody(String body) { - return r -> { - assertThat(r.code()).isEqualTo(200); - assertThat(r.body()).isNotNull(); - assertThat(r.body().string()).isEqualTo(body); - }; + protected static IOConsumer okResponseBody(String body) { + return r -> { + assertThat(r.code()).isEqualTo(200); + assertThat(r.body()).isNotNull(); + assertThat(r.body().string()).isEqualTo(body); + }; + } } -} From 1c5adb34c2aeb24af23a8287f345f9a358cda458 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:27:37 +0100 Subject: [PATCH 6/9] add jms message sending in test app --- smoke-tests/test-app/build.gradle.kts | 4 +- .../java/co/elastic/otel/test/AppMain.java | 3 + .../otel/test/MessagingController.java | 71 +++++++++++++++++++ .../src/main/resources/application.properties | 2 + 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java create mode 100644 smoke-tests/test-app/src/main/resources/application.properties diff --git a/smoke-tests/test-app/build.gradle.kts b/smoke-tests/test-app/build.gradle.kts index d95753af..196dea29 100644 --- a/smoke-tests/test-app/build.gradle.kts +++ b/smoke-tests/test-app/build.gradle.kts @@ -16,12 +16,14 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}") - implementation("io.opentelemetry:opentelemetry-api") implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations") implementation(project(":runtime-attach")) + implementation("org.springframework.boot:spring-boot-starter-artemis:${springBootVersion}") + // using a rather old version to keep java 8 compatibility + implementation("org.apache.activemq:artemis-jms-server:2.27.0") } java { diff --git a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java index 16ad73c5..de38e1a3 100644 --- a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java +++ b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java @@ -21,8 +21,11 @@ import co.elastic.otel.agent.attach.RuntimeAttach; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.EnableJms; @SpringBootApplication +@EnableJms public class AppMain { public static void main(String[] args) { diff --git a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java new file mode 100644 index 00000000..1317dccf --- /dev/null +++ b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java @@ -0,0 +1,71 @@ +package co.elastic.otel.test; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.TextMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import java.util.Enumeration; + +@RestController +@RequestMapping("/messages") +public class MessagingController { + + private static final String DESTINATION = "messages-destination"; + + @Autowired + public MessagingController(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + private final JmsTemplate jmsTemplate; + + @RequestMapping("/send") + public String send( + @RequestParam(name = "headerName", required = false) String headerName, + @RequestParam(name= "headerValue", required = false) String headerValue) { + jmsTemplate.send(DESTINATION, session -> { + TextMessage message = session.createTextMessage("Hello World"); + if (headerName != null && headerValue != null) { + message.setStringProperty(headerName, headerValue); + } + return message; + }); + return null; + } + + @RequestMapping("/receive") + public String receive() throws JMSException { + Message received = jmsTemplate.receive(DESTINATION); + if (received instanceof TextMessage) { + TextMessage textMessage = (TextMessage) received; + StringBuilder sb = new StringBuilder(); + sb.append("message: [") + .append(textMessage.getText()) + .append("]"); + + Enumeration propertyNames = textMessage.getPropertyNames(); + if(propertyNames.hasMoreElements()) { + sb.append(", headers: ["); + int count = 0; + while (propertyNames.hasMoreElements()) { + String propertyName = (String) propertyNames.nextElement(); + sb.append(count++ > 0 ? ", " : "") + .append(propertyName) + .append(" = ") + .append(textMessage.getStringProperty(propertyName)); + } + sb.append("]"); + + } + + return sb.toString(); + } else { + return "nothing received"; + } + } + +} diff --git a/smoke-tests/test-app/src/main/resources/application.properties b/smoke-tests/test-app/src/main/resources/application.properties new file mode 100644 index 00000000..54c62a36 --- /dev/null +++ b/smoke-tests/test-app/src/main/resources/application.properties @@ -0,0 +1,2 @@ +# use an in-process activemq artemis instance +spring.artemis.mode=embedded From 30c461dcb0bb91a52429788a2f156d0ed7eb68d9 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:57:14 +0100 Subject: [PATCH 7/9] implement header test for messaging --- .../smoketest/AgentFeaturesSmokeTest.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java index 1b4eb3a1..6d91f1e3 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java @@ -41,10 +41,15 @@ public static void start() { (container) -> { // capture span stacktrace for any duration container.addEnv("ELASTIC_OTEL_JAVA_SPAN_STACKTRACE_MIN_DURATION", "0ms"); - // capture HTTP request/response headers on server side + // Capture HTTP request/response headers on server side // header key should not be case-sensitive in config container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_REQUEST_HEADERS", "hello"); - container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_RESPONSE_HEADERS", "Content-length,Date"); + container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_RESPONSE_HEADERS", + "Content-length,Date"); + // Capture messaging headers + // Header name IS case-sensitive, syntax may be limited by implementation, for example JMS + // requires it to be a valid java identifier. + container.addEnv("OTEL_INSTRUMENTATION_MESSAGING_EXPERIMENTAL_CAPTURE_HEADERS", "My_Header"); } ); } @@ -89,6 +94,28 @@ public void httpHeaderCapture() { .containsEntry("http.request.header.hello", attributeArrayValue("World!")) .containsEntry("http.response.header.content-length", attributeArrayValue("6")) .containsKey("http.response.header.date")); + } + + @Test + public void messagingHeaderCapture() { + doRequest(getUrl("/messages/send?headerName=My_Header&headerValue=my-header-value"), + okResponse()); + doRequest(getUrl("/messages/receive"), okResponse()); + + List traces = waitForTraces(); + List spans = getSpans(traces).toList(); + assertThat(spans).hasSize(3) + .extracting("name", "kind") + .containsOnly( + tuple("GET /messages/send", Span.SpanKind.SPAN_KIND_SERVER), + tuple("messages-destination publish", Span.SpanKind.SPAN_KIND_PRODUCER), + tuple("GET /messages/receive", Span.SpanKind.SPAN_KIND_SERVER)); + + spans.stream().filter(span -> span.getKind() == Span.SpanKind.SPAN_KIND_PRODUCER) + .forEach(span -> assertThat(getAttributes(span.getAttributesList())) + .containsEntry("messaging.destination.name", attributeValue("messages-destination")) + .containsEntry("messaging.header.My_Header", attributeArrayValue("my-header-value"))); + } } From ba459e27e404da6a4112b19d636f285b1a2e8b98 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:59:13 +0100 Subject: [PATCH 8/9] code cleanup --- .../smoketest/AgentFeaturesSmokeTest.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java index 6d91f1e3..4f019815 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java @@ -21,10 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; -import com.google.protobuf.Any; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; -import io.opentelemetry.proto.common.v1.AnyValue; -import io.opentelemetry.proto.common.v1.ArrayValue; import io.opentelemetry.proto.trace.v1.Span; import java.util.HashMap; import java.util.List; @@ -36,7 +33,7 @@ class AgentFeaturesSmokeTest extends TestAppSmokeTest { @BeforeAll - public static void start() { + static void start() { startTestApp( (container) -> { // capture span stacktrace for any duration @@ -54,15 +51,13 @@ public static void start() { ); } - // TODO remove useless public modifiers ? - @AfterAll - public static void end() { + static void end() { stopApp(); } @Test - public void spanCodeStackTrace() { + void spanCodeStackTrace() { doRequest(getUrl("/health"), okResponseBody("Alive!")); List traces = waitForTraces(); @@ -78,7 +73,7 @@ public void spanCodeStackTrace() { } @Test - public void httpHeaderCapture() { + void httpHeaderCapture() { Map headers = new HashMap<>(); headers.put("Hello", "World!"); doRequest(getUrl("/health"), headers, okResponseBody("Alive!")); @@ -97,7 +92,7 @@ public void httpHeaderCapture() { } @Test - public void messagingHeaderCapture() { + void messagingHeaderCapture() { doRequest(getUrl("/messages/send?headerName=My_Header&headerValue=my-header-value"), okResponse()); doRequest(getUrl("/messages/receive"), okResponse()); From c6c4df1ca90da74319a3a18d43727cf3f990ec4a Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:16:26 +0100 Subject: [PATCH 9/9] code lint --- .../smoketest/AgentFeaturesSmokeTest.java | 46 ++++++++++-------- .../javaagent/smoketest/JavaExecutable.java | 4 +- .../javaagent/smoketest/SmokeTest.java | 48 ++++++++++--------- .../java/co/elastic/otel/test/AppMain.java | 1 - .../otel/test/MessagingController.java | 46 ++++++++++++------ 5 files changed, 83 insertions(+), 62 deletions(-) diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java index 4f019815..8face47f 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java @@ -41,14 +41,14 @@ static void start() { // Capture HTTP request/response headers on server side // header key should not be case-sensitive in config container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_REQUEST_HEADERS", "hello"); - container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_RESPONSE_HEADERS", - "Content-length,Date"); + container.addEnv( + "OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_RESPONSE_HEADERS", "Content-length,Date"); // Capture messaging headers // Header name IS case-sensitive, syntax may be limited by implementation, for example JMS // requires it to be a valid java identifier. - container.addEnv("OTEL_INSTRUMENTATION_MESSAGING_EXPERIMENTAL_CAPTURE_HEADERS", "My_Header"); - } - ); + container.addEnv( + "OTEL_INSTRUMENTATION_MESSAGING_EXPERIMENTAL_CAPTURE_HEADERS", "My_Header"); + }); } @AfterAll @@ -68,8 +68,8 @@ void spanCodeStackTrace() { .containsOnly(tuple("GET /health", Span.SpanKind.SPAN_KIND_SERVER)); spans.forEach( - span -> assertThat(getAttributes(span.getAttributesList())) - .containsKeys("code.stacktrace")); + span -> + assertThat(getAttributes(span.getAttributesList())).containsKeys("code.stacktrace")); } @Test @@ -85,32 +85,38 @@ void httpHeaderCapture() { .extracting("name", "kind") .containsOnly(tuple("GET /health", Span.SpanKind.SPAN_KIND_SERVER)); - spans.forEach(span -> assertThat(getAttributes(span.getAttributesList())) - .containsEntry("http.request.header.hello", attributeArrayValue("World!")) - .containsEntry("http.response.header.content-length", attributeArrayValue("6")) - .containsKey("http.response.header.date")); + spans.forEach( + span -> + assertThat(getAttributes(span.getAttributesList())) + .containsEntry("http.request.header.hello", attributeArrayValue("World!")) + .containsEntry("http.response.header.content-length", attributeArrayValue("6")) + .containsKey("http.response.header.date")); } @Test void messagingHeaderCapture() { - doRequest(getUrl("/messages/send?headerName=My_Header&headerValue=my-header-value"), - okResponse()); + doRequest( + getUrl("/messages/send?headerName=My_Header&headerValue=my-header-value"), okResponse()); doRequest(getUrl("/messages/receive"), okResponse()); List traces = waitForTraces(); List spans = getSpans(traces).toList(); - assertThat(spans).hasSize(3) + assertThat(spans) + .hasSize(3) .extracting("name", "kind") .containsOnly( tuple("GET /messages/send", Span.SpanKind.SPAN_KIND_SERVER), tuple("messages-destination publish", Span.SpanKind.SPAN_KIND_PRODUCER), tuple("GET /messages/receive", Span.SpanKind.SPAN_KIND_SERVER)); - spans.stream().filter(span -> span.getKind() == Span.SpanKind.SPAN_KIND_PRODUCER) - .forEach(span -> assertThat(getAttributes(span.getAttributesList())) - .containsEntry("messaging.destination.name", attributeValue("messages-destination")) - .containsEntry("messaging.header.My_Header", attributeArrayValue("my-header-value"))); - - + spans.stream() + .filter(span -> span.getKind() == Span.SpanKind.SPAN_KIND_PRODUCER) + .forEach( + span -> + assertThat(getAttributes(span.getAttributesList())) + .containsEntry( + "messaging.destination.name", attributeValue("messages-destination")) + .containsEntry( + "messaging.header.My_Header", attributeArrayValue("my-header-value"))); } } diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java index be4f428c..f5c1879f 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java @@ -78,9 +78,7 @@ private static boolean probeListeningDebugger(int port) { Process process = new ProcessBuilder() .command( - JavaExecutable.getBinaryPath(), - jvmDebugArgument("localhost", port), - "-version") + JavaExecutable.getBinaryPath(), jvmDebugArgument("localhost", port), "-version") .start(); boolean processExit = process.waitFor(5, TimeUnit.SECONDS); return processExit && process.exitValue() == 0; diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java index 14492572..363b6dfe 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java @@ -357,7 +357,7 @@ protected static AnyValue attributeValue(String value) { return AnyValue.newBuilder().setStringValue(value).build(); } - protected static AnyValue attributeArrayValue(String... values){ + protected static AnyValue attributeArrayValue(String... values) { ArrayValue.Builder valueBuilder = ArrayValue.newBuilder(); for (String value : values) { valueBuilder.addValues(AnyValue.newBuilder().setStringValue(value).build()); @@ -391,37 +391,39 @@ protected static String bytesToHex(byte[] bytes) { } return sb.toString(); } + protected void doRequest(String url, IOConsumer responseHandler) { doRequest(url, Collections.emptyMap(), responseHandler); } - protected void doRequest(String url, Map headers, IOConsumer responseHandler) { + protected void doRequest( + String url, Map headers, IOConsumer responseHandler) { Request.Builder request = new Request.Builder().url(url).get(); headers.forEach(request::addHeader); - try (Response response = client.newCall(request.build()).execute()) { - responseHandler.accept(response); - } catch (IOException e) { - throw new RuntimeException(e); - } + try (Response response = client.newCall(request.build()).execute()) { + responseHandler.accept(response); + } catch (IOException e) { + throw new RuntimeException(e); } + } - @FunctionalInterface - protected interface IOConsumer { - void accept(T t) throws IOException; - } + @FunctionalInterface + protected interface IOConsumer { + void accept(T t) throws IOException; + } - protected static IOConsumer okResponse() { - return r -> { - assertThat(r.code()).isEqualTo(200); - }; - } + protected static IOConsumer okResponse() { + return r -> { + assertThat(r.code()).isEqualTo(200); + }; + } - protected static IOConsumer okResponseBody(String body) { - return r -> { - assertThat(r.code()).isEqualTo(200); - assertThat(r.body()).isNotNull(); - assertThat(r.body().string()).isEqualTo(body); - }; - } + protected static IOConsumer okResponseBody(String body) { + return r -> { + assertThat(r.code()).isEqualTo(200); + assertThat(r.body()).isNotNull(); + assertThat(r.body().string()).isEqualTo(body); + }; } +} diff --git a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java index de38e1a3..37eabca2 100644 --- a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java +++ b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java @@ -21,7 +21,6 @@ import co.elastic.otel.agent.attach.RuntimeAttach; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Configuration; import org.springframework.jms.annotation.EnableJms; @SpringBootApplication diff --git a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java index 1317dccf..773e227c 100644 --- a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java +++ b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/MessagingController.java @@ -1,5 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.test; +import java.util.Enumeration; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.TextMessage; @@ -8,7 +27,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.Enumeration; @RestController @RequestMapping("/messages") @@ -26,14 +44,16 @@ public MessagingController(JmsTemplate jmsTemplate) { @RequestMapping("/send") public String send( @RequestParam(name = "headerName", required = false) String headerName, - @RequestParam(name= "headerValue", required = false) String headerValue) { - jmsTemplate.send(DESTINATION, session -> { - TextMessage message = session.createTextMessage("Hello World"); - if (headerName != null && headerValue != null) { - message.setStringProperty(headerName, headerValue); - } - return message; - }); + @RequestParam(name = "headerValue", required = false) String headerValue) { + jmsTemplate.send( + DESTINATION, + session -> { + TextMessage message = session.createTextMessage("Hello World"); + if (headerName != null && headerValue != null) { + message.setStringProperty(headerName, headerValue); + } + return message; + }); return null; } @@ -43,12 +63,10 @@ public String receive() throws JMSException { if (received instanceof TextMessage) { TextMessage textMessage = (TextMessage) received; StringBuilder sb = new StringBuilder(); - sb.append("message: [") - .append(textMessage.getText()) - .append("]"); + sb.append("message: [").append(textMessage.getText()).append("]"); Enumeration propertyNames = textMessage.getPropertyNames(); - if(propertyNames.hasMoreElements()) { + if (propertyNames.hasMoreElements()) { sb.append(", headers: ["); int count = 0; while (propertyNames.hasMoreElements()) { @@ -59,7 +77,6 @@ public String receive() throws JMSException { .append(textMessage.getStringProperty(propertyName)); } sb.append("]"); - } return sb.toString(); @@ -67,5 +84,4 @@ public String receive() throws JMSException { return "nothing received"; } } - }