Skip to content

Commit 60cb6c9

Browse files
authored
test header capture (#895)
1 parent 2827c26 commit 60cb6c9

File tree

9 files changed

+188
-82
lines changed

9 files changed

+188
-82
lines changed

common/src/main/java/co/elastic/otel/common/ElasticAttributes.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@
2222
import java.util.List;
2323

2424
public interface ElasticAttributes {
25-
AttributeKey<Long> SELF_TIME = AttributeKey.longKey("elastic.span.self_time");
26-
AttributeKey<String> LOCAL_ROOT_ID = AttributeKey.stringKey("elastic.span.local_root.id");
27-
AttributeKey<String> LOCAL_ROOT_NAME = AttributeKey.stringKey("elastic.local_root.name");
28-
AttributeKey<String> LOCAL_ROOT_TYPE = AttributeKey.stringKey("elastic.local_root.type");
29-
AttributeKey<Boolean> IS_LOCAL_ROOT = AttributeKey.booleanKey("elastic.span.is_local_root");
30-
AttributeKey<String> SPAN_TYPE = AttributeKey.stringKey("elastic.span.type");
31-
AttributeKey<String> SPAN_SUBTYPE = AttributeKey.stringKey("elastic.span.subtype");
3225

3326
/**
3427
* Marker attribute for inferred spans. Does not have the elastic-prefix anymore because it has

custom/breakdown-metrics.md

Lines changed: 0 additions & 48 deletions
This file was deleted.

smoke-tests/src/test/java/com/example/javaagent/smoketest/AgentFeaturesSmokeTest.java

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,41 @@
2323

2424
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
2525
import io.opentelemetry.proto.trace.v1.Span;
26+
import java.util.HashMap;
2627
import java.util.List;
28+
import java.util.Map;
2729
import org.junit.jupiter.api.AfterAll;
2830
import org.junit.jupiter.api.BeforeAll;
2931
import org.junit.jupiter.api.Test;
3032

3133
class AgentFeaturesSmokeTest extends TestAppSmokeTest {
3234

3335
@BeforeAll
34-
public static void start() {
36+
static void start() {
3537
startTestApp(
36-
(container) -> container.addEnv("ELASTIC_OTEL_JAVA_SPAN_STACKTRACE_MIN_DURATION", "0ms"));
38+
(container) -> {
39+
// capture span stacktrace for any duration
40+
container.addEnv("ELASTIC_OTEL_JAVA_SPAN_STACKTRACE_MIN_DURATION", "0ms");
41+
// Capture HTTP request/response headers on server side
42+
// header key should not be case-sensitive in config
43+
container.addEnv("OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_REQUEST_HEADERS", "hello");
44+
container.addEnv(
45+
"OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_RESPONSE_HEADERS", "Content-length,Date");
46+
// Capture messaging headers
47+
// Header name IS case-sensitive, syntax may be limited by implementation, for example JMS
48+
// requires it to be a valid java identifier.
49+
container.addEnv(
50+
"OTEL_INSTRUMENTATION_MESSAGING_EXPERIMENTAL_CAPTURE_HEADERS", "My_Header");
51+
});
3752
}
3853

3954
@AfterAll
40-
public static void end() {
55+
static void end() {
4156
stopApp();
4257
}
4358

4459
@Test
45-
public void healthcheck() throws InterruptedException {
60+
void spanCodeStackTrace() {
4661
doRequest(getUrl("/health"), okResponseBody("Alive!"));
4762

4863
List<ExportTraceServiceRequest> traces = waitForTraces();
@@ -53,14 +68,55 @@ public void healthcheck() throws InterruptedException {
5368
.containsOnly(tuple("GET /health", Span.SpanKind.SPAN_KIND_SERVER));
5469

5570
spans.forEach(
56-
span -> {
57-
assertThat(getAttributes(span.getAttributesList()))
58-
// span breakdown feature disabled
59-
.doesNotContainKeys(
60-
"elastic.span.self_time",
61-
"elastic.span.is_local_root",
62-
"elastic.span.local_root.id")
63-
.containsKeys("code.stacktrace");
64-
});
71+
span ->
72+
assertThat(getAttributes(span.getAttributesList())).containsKeys("code.stacktrace"));
73+
}
74+
75+
@Test
76+
void httpHeaderCapture() {
77+
Map<String, String> headers = new HashMap<>();
78+
headers.put("Hello", "World!");
79+
doRequest(getUrl("/health"), headers, okResponseBody("Alive!"));
80+
81+
List<ExportTraceServiceRequest> traces = waitForTraces();
82+
List<Span> spans = getSpans(traces).toList();
83+
assertThat(spans)
84+
.hasSize(1)
85+
.extracting("name", "kind")
86+
.containsOnly(tuple("GET /health", Span.SpanKind.SPAN_KIND_SERVER));
87+
88+
spans.forEach(
89+
span ->
90+
assertThat(getAttributes(span.getAttributesList()))
91+
.containsEntry("http.request.header.hello", attributeArrayValue("World!"))
92+
.containsEntry("http.response.header.content-length", attributeArrayValue("6"))
93+
.containsKey("http.response.header.date"));
94+
}
95+
96+
@Test
97+
void messagingHeaderCapture() {
98+
doRequest(
99+
getUrl("/messages/send?headerName=My_Header&headerValue=my-header-value"), okResponse());
100+
doRequest(getUrl("/messages/receive"), okResponse());
101+
102+
List<ExportTraceServiceRequest> traces = waitForTraces();
103+
List<Span> spans = getSpans(traces).toList();
104+
assertThat(spans)
105+
.hasSize(3)
106+
.extracting("name", "kind")
107+
.containsOnly(
108+
tuple("GET /messages/send", Span.SpanKind.SPAN_KIND_SERVER),
109+
tuple("messages-destination publish", Span.SpanKind.SPAN_KIND_PRODUCER),
110+
tuple("GET /messages/receive", Span.SpanKind.SPAN_KIND_SERVER));
111+
112+
spans.stream()
113+
.filter(span -> span.getKind() == Span.SpanKind.SPAN_KIND_PRODUCER)
114+
.forEach(
115+
span ->
116+
assertThat(getAttributes(span.getAttributesList()))
117+
.containsEntry(
118+
"messaging.destination.name", attributeValue("messages-destination"))
119+
.containsEntry(
120+
"messaging.header.My_Header", attributeArrayValue("my-header-value")));
65121
}
66122
}

smoke-tests/src/test/java/com/example/javaagent/smoketest/JavaExecutable.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,18 @@ public static boolean isDebugInCI() {
7070

7171
private static boolean probeListeningDebugger(int port) {
7272
// the most straightforward way to probe for an active debugger listening on port is to start
73-
// another JVM
74-
// with the debug options and check the process exit status. Trying to probe for open network
75-
// port messes with
76-
// the debugger and makes IDEA stop it. The only downside of this is that the debugger will
77-
// first attach to this
78-
// probe JVM, then the one running in a docker container we are aiming to debug.
73+
// another JVM with the debug options and check the process exit status. Trying to probe for
74+
// open network port messes with the debugger and makes IDEA stop it. The only downside of this
75+
// is that the debugger will first attach to this probe JVM, then the one running in a docker
76+
// container we are aiming to debug.
7977
try {
8078
Process process =
8179
new ProcessBuilder()
8280
.command(
83-
JavaExecutable.getBinaryPath().toString(),
84-
jvmDebugArgument("localhost", port),
85-
"-version")
81+
JavaExecutable.getBinaryPath(), jvmDebugArgument("localhost", port), "-version")
8682
.start();
87-
process.waitFor(5, TimeUnit.SECONDS);
88-
return process.exitValue() == 0;
83+
boolean processExit = process.waitFor(5, TimeUnit.SECONDS);
84+
return processExit && process.exitValue() == 0;
8985
} catch (InterruptedException | IOException e) {
9086
return false;
9187
}

smoke-tests/src/test/java/com/example/javaagent/smoketest/SmokeTest.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.protobuf.util.JsonFormat;
2727
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
2828
import io.opentelemetry.proto.common.v1.AnyValue;
29+
import io.opentelemetry.proto.common.v1.ArrayValue;
2930
import io.opentelemetry.proto.common.v1.KeyValue;
3031
import io.opentelemetry.proto.resource.v1.Resource;
3132
import io.opentelemetry.proto.trace.v1.ResourceSpans;
@@ -34,6 +35,7 @@
3435
import java.time.Duration;
3536
import java.util.ArrayList;
3637
import java.util.Collection;
38+
import java.util.Collections;
3739
import java.util.HashMap;
3840
import java.util.List;
3941
import java.util.Map;
@@ -355,6 +357,14 @@ protected static AnyValue attributeValue(String value) {
355357
return AnyValue.newBuilder().setStringValue(value).build();
356358
}
357359

360+
protected static AnyValue attributeArrayValue(String... values) {
361+
ArrayValue.Builder valueBuilder = ArrayValue.newBuilder();
362+
for (String value : values) {
363+
valueBuilder.addValues(AnyValue.newBuilder().setStringValue(value).build());
364+
}
365+
return AnyValue.newBuilder().setArrayValue(valueBuilder.build()).build();
366+
}
367+
358368
protected static Map<String, AnyValue> getAttributes(List<KeyValue> list) {
359369
Map<String, AnyValue> attributes = new HashMap<>();
360370
for (KeyValue kv : list) {
@@ -383,9 +393,15 @@ protected static String bytesToHex(byte[] bytes) {
383393
}
384394

385395
protected void doRequest(String url, IOConsumer<Response> responseHandler) {
386-
Request request = new Request.Builder().url(url).get().build();
396+
doRequest(url, Collections.emptyMap(), responseHandler);
397+
}
398+
399+
protected void doRequest(
400+
String url, Map<String, String> headers, IOConsumer<Response> responseHandler) {
401+
Request.Builder request = new Request.Builder().url(url).get();
402+
headers.forEach(request::addHeader);
387403

388-
try (Response response = client.newCall(request).execute()) {
404+
try (Response response = client.newCall(request.build()).execute()) {
389405
responseHandler.accept(response);
390406
} catch (IOException e) {
391407
throw new RuntimeException(e);

smoke-tests/test-app/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ dependencies {
1616
testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
1717
implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
1818

19-
2019
implementation("io.opentelemetry:opentelemetry-api")
2120
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations")
2221

2322
implementation(project(":runtime-attach"))
2423

24+
implementation("org.springframework.boot:spring-boot-starter-artemis:${springBootVersion}")
25+
// using a rather old version to keep java 8 compatibility
26+
implementation("org.apache.activemq:artemis-jms-server:2.27.0")
2527
}
2628

2729
java {

smoke-tests/test-app/src/main/java/co/elastic/otel/test/AppMain.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import co.elastic.otel.agent.attach.RuntimeAttach;
2222
import org.springframework.boot.SpringApplication;
2323
import org.springframework.boot.autoconfigure.SpringBootApplication;
24+
import org.springframework.jms.annotation.EnableJms;
2425

2526
@SpringBootApplication
27+
@EnableJms
2628
public class AppMain {
2729

2830
public static void main(String[] args) {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.otel.test;
20+
21+
import java.util.Enumeration;
22+
import javax.jms.JMSException;
23+
import javax.jms.Message;
24+
import javax.jms.TextMessage;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.jms.core.JmsTemplate;
27+
import org.springframework.web.bind.annotation.RequestMapping;
28+
import org.springframework.web.bind.annotation.RequestParam;
29+
import org.springframework.web.bind.annotation.RestController;
30+
31+
@RestController
32+
@RequestMapping("/messages")
33+
public class MessagingController {
34+
35+
private static final String DESTINATION = "messages-destination";
36+
37+
@Autowired
38+
public MessagingController(JmsTemplate jmsTemplate) {
39+
this.jmsTemplate = jmsTemplate;
40+
}
41+
42+
private final JmsTemplate jmsTemplate;
43+
44+
@RequestMapping("/send")
45+
public String send(
46+
@RequestParam(name = "headerName", required = false) String headerName,
47+
@RequestParam(name = "headerValue", required = false) String headerValue) {
48+
jmsTemplate.send(
49+
DESTINATION,
50+
session -> {
51+
TextMessage message = session.createTextMessage("Hello World");
52+
if (headerName != null && headerValue != null) {
53+
message.setStringProperty(headerName, headerValue);
54+
}
55+
return message;
56+
});
57+
return null;
58+
}
59+
60+
@RequestMapping("/receive")
61+
public String receive() throws JMSException {
62+
Message received = jmsTemplate.receive(DESTINATION);
63+
if (received instanceof TextMessage) {
64+
TextMessage textMessage = (TextMessage) received;
65+
StringBuilder sb = new StringBuilder();
66+
sb.append("message: [").append(textMessage.getText()).append("]");
67+
68+
Enumeration<?> propertyNames = textMessage.getPropertyNames();
69+
if (propertyNames.hasMoreElements()) {
70+
sb.append(", headers: [");
71+
int count = 0;
72+
while (propertyNames.hasMoreElements()) {
73+
String propertyName = (String) propertyNames.nextElement();
74+
sb.append(count++ > 0 ? ", " : "")
75+
.append(propertyName)
76+
.append(" = ")
77+
.append(textMessage.getStringProperty(propertyName));
78+
}
79+
sb.append("]");
80+
}
81+
82+
return sb.toString();
83+
} else {
84+
return "nothing received";
85+
}
86+
}
87+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# use an in-process activemq artemis instance
2+
spring.artemis.mode=embedded

0 commit comments

Comments
 (0)