Skip to content

Commit 8acd694

Browse files
authored
Add API methods to enable RUM trace correlation (#278)
1 parent 5288f2f commit 8acd694

File tree

11 files changed

+201
-0
lines changed

11 files changed

+201
-0
lines changed

apm-agent-api/src/main/java/co/elastic/apm/api/NoopSpan.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ public String getId() {
5555
return "";
5656
}
5757

58+
@Nonnull
59+
@Override
60+
public String getTraceId() {
61+
return "";
62+
}
63+
5864
@Override
5965
public Scope activate() {
6066
return NoopScope.INSTANCE;

apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ public String getId() {
6161
return "";
6262
}
6363

64+
@Nonnull
65+
@Override
66+
public String ensureParentId() {
67+
return "";
68+
}
69+
70+
@Nonnull
71+
@Override
72+
public String getTraceId() {
73+
return "";
74+
}
75+
6476
@Override
6577
public Scope activate() {
6678
return NoopScope.INSTANCE;

apm-agent-api/src/main/java/co/elastic/apm/api/Span.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,22 @@ public interface Span {
132132
@Nonnull
133133
String getId();
134134

135+
/**
136+
* Returns the id of this trace (never {@code null})
137+
* <p>
138+
* The trace-ID is consistent across all transactions and spans which belong to the same logical trace,
139+
* even for spans which happened in another service (given this service is also monitored by Elastic APM).
140+
* </p>
141+
* <p>
142+
* If this span represents a noop,
143+
* this method returns an empty string.
144+
* </p>
145+
*
146+
* @return the id of this span (never {@code null})
147+
*/
148+
@Nonnull
149+
String getTraceId();
150+
135151
/**
136152
* Makes this span the active span on the current thread until {@link Scope#close()} has been called.
137153
* <p>

apm-agent-api/src/main/java/co/elastic/apm/api/SpanImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ public String getId() {
8282
return "";
8383
}
8484

85+
@Nonnull
86+
@Override
87+
public String getTraceId() {
88+
// co.elastic.apm.plugin.api.SpanInstrumentation.GetTraceIdInstrumentation
89+
return "";
90+
}
91+
8592
@Override
8693
public Scope activate() {
8794
// co.elastic.apm.plugin.api.SpanInstrumentation.ActivateInstrumentation

apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,50 @@ public interface Transaction extends Span {
117117
@Nonnull
118118
String getId();
119119

120+
/**
121+
* <p>
122+
* If the transaction does not have a parent-ID yet,
123+
* calling this method generates a new ID,
124+
* sets it as the parent-ID of this transaction,
125+
* and returns it as a `String`.
126+
* </p>
127+
* <p>
128+
* This enables the correlation of the spans the JavaScript Real User Monitoring (RUM) agent creates for the initial page load
129+
* with the transaction of the backend service.
130+
* If your backend service generates the HTML page dynamically,
131+
* initializing the JavaScript RUM agent with the value of this method allows analyzing the time spent in the browser vs in the backend services.
132+
* </p>
133+
* <p>
134+
* To enable the JavaScript RUM agent when using an HTML templating language like Freemarker,
135+
* add {@code ElasticApm.currentTransaction()} with the key {@code "transaction"} to the model.
136+
* </p>
137+
* <p>
138+
* Also, add a snippet similar to this to the body of your HTML pages,
139+
* preferably before other JS libraries:
140+
* </p>
141+
*
142+
* <pre>{@code
143+
* <script src="elastic-apm-js-base/dist/bundles/elastic-apm-js-base.umd.min.js"></script>
144+
* <script>
145+
* var elasticApm = initApm({
146+
* serviceName: '',
147+
* serverUrl: 'http://localhost:8200',
148+
* pageLoadTraceId: "${transaction.traceId}",
149+
* pageLoadSpanId: "${transaction.ensureParentId()}",
150+
* pageLoadSampled: ${transaction.sampled}
151+
* })
152+
* </script>
153+
* }</pre>
154+
*
155+
* <p>
156+
* See the JavaScript RUM agent documentation for more information.
157+
* </p>
158+
*
159+
* @return the parent-ID for this transaction. Updates the transaction to use a new parent-ID if it has previously been unset.
160+
*/
161+
@Nonnull
162+
String ensureParentId();
163+
120164
/**
121165
* Makes this transaction the active transaction on the current thread until {@link Scope#close()} has been called.
122166
* <p>

apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,11 @@ public void setUser(String id, String email, String username) {
4040
// co.elastic.apm.plugin.api.TransactionInstrumentation$SetUserInstrumentation.setUser
4141
}
4242

43+
@Nonnull
44+
@Override
45+
public String ensureParentId() {
46+
// co.elastic.apm.plugin.api.TransactionInstrumentation.EnsureParentIdInstrumentation
47+
return "";
48+
}
49+
4350
}

apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/SpanInstrumentation.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,21 @@ public static void getId(@Advice.FieldValue(value = "span", typing = Assigner.Ty
146146
}
147147
}
148148

149+
public static class GetTraceIdInstrumentation extends SpanInstrumentation {
150+
public GetTraceIdInstrumentation() {
151+
super(named("getTraceId").and(takesArguments(0)));
152+
}
153+
154+
@VisibleForAdvice
155+
@Advice.OnMethodExit
156+
public static void getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan<?> span,
157+
@Advice.Return(readOnly = false) String traceId) {
158+
if (tracer != null) {
159+
traceId = span.getTraceContext().getTraceId().toString();
160+
}
161+
}
162+
}
163+
149164
public static class AddTagInstrumentation extends SpanInstrumentation {
150165
public AddTagInstrumentation() {
151166
super(named("addTag"));

apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/TransactionInstrumentation.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import co.elastic.apm.bci.ElasticApmInstrumentation;
2323
import co.elastic.apm.bci.VisibleForAdvice;
24+
import co.elastic.apm.impl.transaction.TraceContext;
2425
import co.elastic.apm.impl.transaction.Transaction;
2526
import net.bytebuddy.asm.Advice;
2627
import net.bytebuddy.description.method.MethodDescription;
@@ -77,4 +78,23 @@ public static void setUser(@Advice.FieldValue(value = "span", typing = Assigner.
7778
transaction.setUser(id, email, username);
7879
}
7980
}
81+
82+
public static class EnsureParentIdInstrumentation extends TransactionInstrumentation {
83+
public EnsureParentIdInstrumentation() {
84+
super(named("ensureParentId"));
85+
}
86+
87+
@VisibleForAdvice
88+
@Advice.OnMethodExit
89+
public static void ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
90+
@Advice.Return(readOnly = false) String spanId) {
91+
if (tracer != null) {
92+
final TraceContext traceContext = transaction.getTraceContext();
93+
if (traceContext.getParentId().isEmpty()) {
94+
traceContext.getParentId().setToRandomValue();
95+
}
96+
spanId = traceContext.getParentId().toString();
97+
}
98+
}
99+
}
80100
}

apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ co.elastic.apm.plugin.api.ElasticApmApiInstrumentation$CurrentTransactionInstrum
33
co.elastic.apm.plugin.api.ElasticApmApiInstrumentation$CurrentSpanInstrumentation
44
co.elastic.apm.plugin.api.ElasticApmApiInstrumentation$CaptureExceptionInstrumentation
55
co.elastic.apm.plugin.api.TransactionInstrumentation$SetUserInstrumentation
6+
co.elastic.apm.plugin.api.TransactionInstrumentation$EnsureParentIdInstrumentation
67
co.elastic.apm.plugin.api.SpanInstrumentation$SetNameInstrumentation
78
co.elastic.apm.plugin.api.SpanInstrumentation$SetTypeInstrumentation
89
co.elastic.apm.plugin.api.SpanInstrumentation$DoCreateSpanInstrumentation
910
co.elastic.apm.plugin.api.SpanInstrumentation$EndInstrumentation
1011
co.elastic.apm.plugin.api.SpanInstrumentation$CaptureExceptionInstrumentation
1112
co.elastic.apm.plugin.api.SpanInstrumentation$GetIdInstrumentation
13+
co.elastic.apm.plugin.api.SpanInstrumentation$GetTraceIdInstrumentation
1214
co.elastic.apm.plugin.api.SpanInstrumentation$AddTagInstrumentation
1315
co.elastic.apm.plugin.api.SpanInstrumentation$ActivateInstrumentation
1416
co.elastic.apm.plugin.api.CaptureExceptionInstrumentation

apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,13 @@ void testGetId_distributedTracingEnabled() {
108108
co.elastic.apm.impl.transaction.Transaction transaction = tracer.startTransaction().withType(Transaction.TYPE_REQUEST);
109109
try (Scope scope = transaction.activateInScope()) {
110110
assertThat(ElasticApm.currentTransaction().getId()).isEqualTo(transaction.getTraceContext().getId().toString());
111+
assertThat(ElasticApm.currentTransaction().getTraceId()).isEqualTo(transaction.getTraceContext().getTraceId().toString());
111112
assertThat(ElasticApm.currentSpan().getId()).isEqualTo(transaction.getTraceContext().getId().toString());
113+
assertThat(ElasticApm.currentSpan().getTraceId()).isEqualTo(transaction.getTraceContext().getTraceId().toString());
112114
co.elastic.apm.impl.transaction.Span span = transaction.createSpan().withType("db").withName("SELECT");
113115
try (Scope spanScope = span.activateInScope()) {
114116
assertThat(ElasticApm.currentSpan().getId()).isEqualTo(span.getTraceContext().getId().toString());
117+
assertThat(ElasticApm.currentSpan().getTraceId()).isEqualTo(span.getTraceContext().getTraceId().toString());
115118
} finally {
116119
span.end();
117120
}
@@ -162,4 +165,16 @@ void testScopes() {
162165
assertThat(ElasticApm.currentTransaction()).isSameAs(NoopTransaction.INSTANCE);
163166

164167
}
168+
169+
@Test
170+
void testEnsureParentId() {
171+
final Transaction transaction = ElasticApm.startTransaction();
172+
try (co.elastic.apm.api.Scope scope = transaction.activate()) {
173+
assertThat(tracer.currentTransaction()).isNotNull();
174+
assertThat(tracer.currentTransaction().getTraceContext().getParentId().isEmpty()).isTrue();
175+
String rumTransactionId = transaction.ensureParentId();
176+
assertThat(tracer.currentTransaction().getTraceContext().getParentId().toString()).isEqualTo(rumTransactionId);
177+
assertThat(transaction.ensureParentId()).isEqualTo(rumTransactionId);
178+
}
179+
}
165180
}

0 commit comments

Comments
 (0)