Skip to content

Commit 67515c2

Browse files
authored
Adding destination to Messaging transactions and spans (#906)
1 parent 35e25f8 commit 67515c2

File tree

17 files changed

+261
-30
lines changed

17 files changed

+261
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Logs message when `transaction_max_spans` has been exceeded (#849)
1616
* Report the number of affected rows by a SQL statement (UPDATE,DELETE,INSERT) in 'affected_rows' span attribute (#707)
1717
* Add [`@Traced`](https://www.elastic.co/guide/en/apm/agent/java/master/public-api.html#api-traced) annotation which either creates a span or a transaction, depending on the context
18+
* Report JMS destination as a span/transaction context field (#906)
1819

1920
## Bug Fixes
2021
* JMS creates polling transactions even when the API invocations return without a message

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/AbstractContext.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public abstract class AbstractContext implements Recyclable {
4040
*/
4141
private final Map<String, Object> labels = new ConcurrentHashMap<>();
4242

43+
/**
44+
* An object containing contextual data for Messages (incoming in case of transactions or outgoing in case of spans)
45+
*/
46+
private final Message message = new Message();
47+
4348
public Iterator<? extends Map.Entry<String, ?>> getLabelIterator() {
4449
return labels.entrySet().iterator();
4550
}
@@ -68,16 +73,22 @@ public boolean hasLabels() {
6873
return !labels.isEmpty();
6974
}
7075

76+
public Message getMessage() {
77+
return message;
78+
}
79+
7180
@Override
7281
public void resetState() {
7382
labels.clear();
83+
message.resetState();
7484
}
7585

7686
public boolean hasContent() {
77-
return !labels.isEmpty();
87+
return !labels.isEmpty() || message.hasContent();
7888
}
7989

8090
public void copyFrom(AbstractContext other) {
8191
labels.putAll(other.labels);
92+
message.copyFrom(other.message);
8293
}
8394
}

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Db.java renamed to apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Db.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* #L%
2424
*/
2525

26-
package co.elastic.apm.agent.impl.transaction;
26+
package co.elastic.apm.agent.impl.context;
2727

2828
import co.elastic.apm.agent.objectpool.Allocator;
2929
import co.elastic.apm.agent.objectpool.ObjectPool;

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Http.java renamed to apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Http.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* under the License.
2323
* #L%
2424
*/
25-
package co.elastic.apm.agent.impl.transaction;
25+
package co.elastic.apm.agent.impl.context;
2626

2727
import co.elastic.apm.agent.objectpool.Recyclable;
2828

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2019 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.apm.agent.impl.context;
26+
27+
import co.elastic.apm.agent.objectpool.Recyclable;
28+
29+
import javax.annotation.Nullable;
30+
31+
public class Message implements Recyclable {
32+
33+
@Nullable
34+
private String queueName;
35+
36+
@Nullable
37+
private String topicName;
38+
39+
@Nullable
40+
public String getQueueName() {
41+
return queueName;
42+
}
43+
44+
public Message withQueue(String queueName) {
45+
this.queueName = queueName;
46+
return this;
47+
}
48+
49+
@Nullable
50+
public String getTopicName() {
51+
return topicName;
52+
}
53+
54+
public Message withTopic(String topicName) {
55+
this.topicName = topicName;
56+
return this;
57+
}
58+
59+
public boolean hasContent() {
60+
return queueName != null || topicName != null;
61+
}
62+
63+
@Override
64+
public void resetState() {
65+
queueName = null;
66+
topicName = null;
67+
}
68+
69+
public void copyFrom(Message other) {
70+
this.queueName = other.getQueueName();
71+
this.topicName = other.getTopicName();
72+
}
73+
}

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/SpanContext.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525

2626
package co.elastic.apm.agent.impl.context;
2727

28-
import co.elastic.apm.agent.impl.transaction.Db;
29-
import co.elastic.apm.agent.impl.transaction.Http;
30-
3128

3229
/**
3330
* Any other arbitrary data captured by the agent, optionally provided by the user

apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import co.elastic.apm.agent.impl.MetaData;
2828
import co.elastic.apm.agent.impl.context.AbstractContext;
29+
import co.elastic.apm.agent.impl.context.Message;
2930
import co.elastic.apm.agent.impl.context.Request;
3031
import co.elastic.apm.agent.impl.context.Response;
3132
import co.elastic.apm.agent.impl.context.Socket;
@@ -46,8 +47,8 @@
4647
import co.elastic.apm.agent.impl.payload.SystemInfo;
4748
import co.elastic.apm.agent.impl.payload.TransactionPayload;
4849
import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration;
49-
import co.elastic.apm.agent.impl.transaction.Db;
50-
import co.elastic.apm.agent.impl.transaction.Http;
50+
import co.elastic.apm.agent.impl.context.Db;
51+
import co.elastic.apm.agent.impl.context.Http;
5152
import co.elastic.apm.agent.impl.transaction.Id;
5253
import co.elastic.apm.agent.impl.transaction.Span;
5354
import co.elastic.apm.agent.impl.transaction.SpanCount;
@@ -723,6 +724,7 @@ private void serializeSpanContext(SpanContext context, TraceContext traceContext
723724
jw.writeByte(OBJECT_START);
724725

725726
serializeServiceName(traceContext);
727+
serializeMessageContext(context.getMessage());
726728
serializeDbContext(context.getDb());
727729
serializeHttpContext(context.getHttp());
728730

@@ -733,6 +735,26 @@ private void serializeSpanContext(SpanContext context, TraceContext traceContext
733735
jw.writeByte(COMMA);
734736
}
735737

738+
private void serializeMessageContext(final Message message) {
739+
if (message.hasContent()) {
740+
writeFieldName("message");
741+
jw.writeByte(OBJECT_START);
742+
if (message.getTopicName() != null) {
743+
writeFieldName("topic");
744+
jw.writeByte(OBJECT_START);
745+
writeLastField("name", message.getTopicName());
746+
jw.writeByte(OBJECT_END);
747+
} else {
748+
writeFieldName("queue");
749+
jw.writeByte(OBJECT_START);
750+
writeLastField("name", message.getQueueName());
751+
jw.writeByte(OBJECT_END);
752+
}
753+
jw.writeByte(OBJECT_END);
754+
jw.writeByte(COMMA);
755+
}
756+
}
757+
736758
private void serializeDbContext(final Db db) {
737759
if (db.hasContent()) {
738760
writeFieldName("db");
@@ -798,6 +820,7 @@ private void serializeContext(final TransactionContext context, TraceContext tra
798820
}
799821
serializeRequest(context.getRequest());
800822
serializeResponse(context.getResponse());
823+
serializeMessageContext(context.getMessage());
801824
if (context.hasCustom()) {
802825
writeFieldName("custom");
803826
serializeStringKeyScalarValueMap(context.getCustomIterator(), replaceBuilder, jw, true, true);

apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ void testSpanTypeSerialization() {
244244
span.withType("template.jsf.render.view");
245245
JsonNode spanJson = readJsonString(serializer.toJsonString(span));
246246
assertThat(spanJson.get("type").textValue()).isEqualTo("template_jsf_render_view");
247+
JsonNode context = spanJson.get("context");
248+
assertThat(context).isNotNull();
249+
assertThat(context.get("message")).isNull();
250+
assertThat(context.get("db")).isNull();
251+
assertThat(context.get("http")).isNull();
247252

248253
span.withType("template").withSubtype("jsf.lifecycle").withAction("render.view");
249254
spanJson = readJsonString(serializer.toJsonString(span));
@@ -268,6 +273,57 @@ void testSpanTypeSerialization() {
268273
assertThat(spanJson.get("type").isNull()).isTrue();
269274
}
270275

276+
@Test
277+
void testSpanHttpContextSerialization() {
278+
Span span = new Span(MockTracer.create());
279+
span.getContext().getHttp()
280+
.withMethod("GET")
281+
.withStatusCode(523)
282+
.withUrl("http://whatever.com/path");
283+
284+
JsonNode spanJson = readJsonString(serializer.toJsonString(span));
285+
JsonNode context = spanJson.get("context");
286+
JsonNode http = context.get("http");
287+
assertThat(http).isNotNull();
288+
assertThat(http.get("method").textValue()).isEqualTo("GET");
289+
assertThat(http.get("url").textValue()).isEqualTo("http://whatever.com/path");
290+
assertThat(http.get("status_code").intValue()).isEqualTo(523);
291+
}
292+
293+
@Test
294+
void testSpanMessageContextSerialization() {
295+
Span span = new Span(MockTracer.create());
296+
span.getContext().getMessage()
297+
.withTopic("test-topic");
298+
299+
JsonNode spanJson = readJsonString(serializer.toJsonString(span));
300+
JsonNode context = spanJson.get("context");
301+
JsonNode message = context.get("message");
302+
assertThat(message).isNotNull();
303+
JsonNode queue = message.get("queue");
304+
assertThat(queue).isNull();
305+
JsonNode topic = message.get("topic");
306+
assertThat(topic).isNotNull();
307+
assertThat("test-topic").isEqualTo(topic.get("name").textValue());
308+
}
309+
310+
@Test
311+
void testSpanDbContextSerialization() {
312+
Span span = new Span(MockTracer.create());
313+
span.getContext().getDb()
314+
.withAffectedRowsCount(5)
315+
.withInstance("test-instance")
316+
.withStatement("SELECT * FROM TABLE").withDbLink("db-link");
317+
318+
JsonNode spanJson = readJsonString(serializer.toJsonString(span));
319+
JsonNode context = spanJson.get("context");
320+
JsonNode db = context.get("db");
321+
assertThat(db).isNotNull();
322+
assertThat(db.get("rows_affected").longValue()).isEqualTo(5);
323+
assertThat(db.get("instance").textValue()).isEqualTo("test-instance");
324+
assertThat(db.get("statement").textValue()).isEqualTo("SELECT * FROM TABLE");
325+
}
326+
271327
@Test
272328
void testInlineReplacement() {
273329
StringBuilder sb = new StringBuilder("this.is.a.string");
@@ -368,7 +424,7 @@ void testConfiguredServiceNodeName() {
368424
}
369425

370426
@Test
371-
void testTransactionContext() {
427+
void testTransactionContextSerialization() {
372428

373429
ElasticApmTracer tracer = MockTracer.create();
374430
Transaction transaction = new Transaction(tracer);
@@ -405,6 +461,8 @@ void testTransactionContext() {
405461
.addHeader("response_header", "value")
406462
.withStatusCode(418);
407463

464+
transaction.getContext().getMessage().withQueue("test_queue");
465+
408466
String jsonString = serializer.toJsonString(transaction);
409467
JsonNode json = readJsonString(jsonString);
410468

@@ -444,6 +502,13 @@ void testTransactionContext() {
444502
assertThat(jsonResponse.get("headers_sent").asBoolean()).isFalse();
445503
assertThat(jsonResponse.get("status_code").asInt()).isEqualTo(418);
446504

505+
JsonNode message = jsonContext.get("message");
506+
assertThat(message).isNotNull();
507+
JsonNode topic = message.get("topic");
508+
assertThat(topic).isNull();
509+
JsonNode queue = message.get("queue");
510+
assertThat(queue).isNotNull();
511+
assertThat("test_queue").isEqualTo(queue.get("name").textValue());
447512
}
448513

449514
private JsonNode readJsonString(String jsonString) {

apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/es/restclient/AbstractEsClientInstrumentationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626

2727
import co.elastic.apm.agent.AbstractInstrumentationTest;
2828
import co.elastic.apm.agent.impl.error.ErrorCapture;
29-
import co.elastic.apm.agent.impl.transaction.Db;
30-
import co.elastic.apm.agent.impl.transaction.Http;
29+
import co.elastic.apm.agent.impl.context.Db;
30+
import co.elastic.apm.agent.impl.context.Http;
3131
import co.elastic.apm.agent.impl.transaction.Span;
3232
import co.elastic.apm.agent.impl.transaction.TraceContext;
3333
import co.elastic.apm.agent.impl.transaction.Transaction;

apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
package co.elastic.apm.agent.jdbc;
2626

2727
import co.elastic.apm.agent.AbstractInstrumentationTest;
28-
import co.elastic.apm.agent.impl.transaction.Db;
28+
import co.elastic.apm.agent.impl.context.Db;
2929
import co.elastic.apm.agent.impl.transaction.Span;
3030
import co.elastic.apm.agent.impl.transaction.TraceContext;
3131
import co.elastic.apm.agent.impl.transaction.Transaction;

0 commit comments

Comments
 (0)