Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apm-agent-api/src/main/java/co/elastic/apm/api/NoopSpan.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public String getId() {
return "";
}

@Nonnull
@Override
public String getTraceId() {
return "";
}

@Override
public Scope activate() {
return NoopScope.INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ public String getId() {
return "";
}

@Nonnull
@Override
public String ensureParentId() {
return "";
}

@Nonnull
@Override
public String getTraceId() {
return "";
}

@Override
public Scope activate() {
return NoopScope.INSTANCE;
Expand Down
16 changes: 16 additions & 0 deletions apm-agent-api/src/main/java/co/elastic/apm/api/Span.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,22 @@ public interface Span {
@Nonnull
String getId();

/**
* Returns the id of this trace (never {@code null})
* <p>
* The trace-ID is consistent across all transactions and spans which belong to the same logical trace,
* even for spans which happened in another service (given this service is also monitored by Elastic APM).
* </p>
* <p>
* If this span represents a noop,
* this method returns an empty string.
* </p>
*
* @return the id of this span (never {@code null})
*/
@Nonnull
String getTraceId();

/**
* Makes this span the active span on the current thread until {@link Scope#close()} has been called.
* <p>
Expand Down
7 changes: 7 additions & 0 deletions apm-agent-api/src/main/java/co/elastic/apm/api/SpanImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ public String getId() {
return "";
}

@Nonnull
@Override
public String getTraceId() {
// co.elastic.apm.plugin.api.SpanInstrumentation.GetTraceIdInstrumentation
return "";
}

@Override
public Scope activate() {
// co.elastic.apm.plugin.api.SpanInstrumentation.ActivateInstrumentation
Expand Down
44 changes: 44 additions & 0 deletions apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,50 @@ public interface Transaction extends Span {
@Nonnull
String getId();

/**
* <p>
* If the transaction does not have a parent-ID yet,
* calling this method generates a new ID,
* sets it as the parent-ID of this transaction,
* and returns it as a `String`.
* </p>
* <p>
* This enables the correlation of the spans the JavaScript Real User Monitoring (RUM) agent creates for the initial page load
* with the transaction of the backend service.
* If your backend service generates the HTML page dynamically,
* initializing the JavaScript RUM agent with the value of this method allows analyzing the time spent in the browser vs in the backend services.
* </p>
* <p>
* To enable the JavaScript RUM agent when using an HTML templating language like Freemarker,
* add {@code ElasticApm.currentTransaction()} with the key {@code "transaction"} to the model.
* </p>
* <p>
* Also, add a snippet similar to this to the body of your HTML pages,
* preferably before other JS libraries:
* </p>
*
* <pre>{@code
* <script src="elastic-apm-js-base/dist/bundles/elastic-apm-js-base.umd.min.js"></script>
* <script>
* var elasticApm = initApm({
* serviceName: '',
* serverUrl: 'http://localhost:8200',
* pageLoadTraceId: "${transaction.traceId}",
* pageLoadSpanId: "${transaction.ensureParentId()}",
* pageLoadSampled: ${transaction.sampled}
* })
* </script>
* }</pre>
*
* <p>
* See the JavaScript RUM agent documentation for more information.
* </p>
*
* @return the parent-ID for this transaction. Updates the transaction to use a new parent-ID if it has previously been unset.
*/
@Nonnull
String ensureParentId();

/**
* Makes this transaction the active transaction on the current thread until {@link Scope#close()} has been called.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ public void setUser(String id, String email, String username) {
// co.elastic.apm.plugin.api.TransactionInstrumentation$SetUserInstrumentation.setUser
}

@Nonnull
@Override
public String ensureParentId() {
// co.elastic.apm.plugin.api.TransactionInstrumentation.EnsureParentIdInstrumentation
return "";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ public static void getId(@Advice.FieldValue(value = "span", typing = Assigner.Ty
}
}

public static class GetTraceIdInstrumentation extends SpanInstrumentation {
public GetTraceIdInstrumentation() {
super(named("getTraceId").and(takesArguments(0)));
}

@VisibleForAdvice
@Advice.OnMethodExit
public static void getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan<?> span,
@Advice.Return(readOnly = false) String traceId) {
if (tracer != null) {
traceId = span.getTraceContext().getTraceId().toString();
}
}
}

public static class AddTagInstrumentation extends SpanInstrumentation {
public AddTagInstrumentation() {
super(named("addTag"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import co.elastic.apm.bci.ElasticApmInstrumentation;
import co.elastic.apm.bci.VisibleForAdvice;
import co.elastic.apm.impl.transaction.TraceContext;
import co.elastic.apm.impl.transaction.Transaction;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
Expand Down Expand Up @@ -77,4 +78,23 @@ public static void setUser(@Advice.FieldValue(value = "span", typing = Assigner.
transaction.setUser(id, email, username);
}
}

public static class EnsureParentIdInstrumentation extends TransactionInstrumentation {
public EnsureParentIdInstrumentation() {
super(named("ensureParentId"));
}

@VisibleForAdvice
@Advice.OnMethodExit
public static void ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
@Advice.Return(readOnly = false) String spanId) {
if (tracer != null) {
final TraceContext traceContext = transaction.getTraceContext();
if (traceContext.getParentId().isEmpty()) {
traceContext.getParentId().setToRandomValue();
}
spanId = traceContext.getParentId().toString();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ co.elastic.apm.plugin.api.ElasticApmApiInstrumentation$CurrentTransactionInstrum
co.elastic.apm.plugin.api.ElasticApmApiInstrumentation$CurrentSpanInstrumentation
co.elastic.apm.plugin.api.ElasticApmApiInstrumentation$CaptureExceptionInstrumentation
co.elastic.apm.plugin.api.TransactionInstrumentation$SetUserInstrumentation
co.elastic.apm.plugin.api.TransactionInstrumentation$EnsureParentIdInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$SetNameInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$SetTypeInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$DoCreateSpanInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$EndInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$CaptureExceptionInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$GetIdInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$GetTraceIdInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$AddTagInstrumentation
co.elastic.apm.plugin.api.SpanInstrumentation$ActivateInstrumentation
co.elastic.apm.plugin.api.CaptureExceptionInstrumentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,13 @@ void testGetId_distributedTracingEnabled() {
co.elastic.apm.impl.transaction.Transaction transaction = tracer.startTransaction().withType(Transaction.TYPE_REQUEST);
try (Scope scope = transaction.activateInScope()) {
assertThat(ElasticApm.currentTransaction().getId()).isEqualTo(transaction.getTraceContext().getId().toString());
assertThat(ElasticApm.currentTransaction().getTraceId()).isEqualTo(transaction.getTraceContext().getTraceId().toString());
assertThat(ElasticApm.currentSpan().getId()).isEqualTo(transaction.getTraceContext().getId().toString());
assertThat(ElasticApm.currentSpan().getTraceId()).isEqualTo(transaction.getTraceContext().getTraceId().toString());
co.elastic.apm.impl.transaction.Span span = transaction.createSpan().withType("db").withName("SELECT");
try (Scope spanScope = span.activateInScope()) {
assertThat(ElasticApm.currentSpan().getId()).isEqualTo(span.getTraceContext().getId().toString());
assertThat(ElasticApm.currentSpan().getTraceId()).isEqualTo(span.getTraceContext().getTraceId().toString());
} finally {
span.end();
}
Expand Down Expand Up @@ -162,4 +165,16 @@ void testScopes() {
assertThat(ElasticApm.currentTransaction()).isSameAs(NoopTransaction.INSTANCE);

}

@Test
void testEnsureParentId() {
final Transaction transaction = ElasticApm.startTransaction();
try (co.elastic.apm.api.Scope scope = transaction.activate()) {
assertThat(tracer.currentTransaction()).isNotNull();
assertThat(tracer.currentTransaction().getTraceContext().getParentId().isEmpty()).isTrue();
String rumTransactionId = transaction.ensureParentId();
assertThat(tracer.currentTransaction().getTraceContext().getParentId().toString()).isEqualTo(rumTransactionId);
assertThat(transaction.ensureParentId()).isEqualTo(rumTransactionId);
}
}
}
57 changes: 57 additions & 0 deletions docs/public-api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,52 @@ Returns the id of this transaction (never `null`)
If this transaction represents a noop,
this method returns an empty string.

[float]
[[api-transaction-get-trace-id]]
==== `String getTraceId()`
Returns the trace-id of this transaction.

The trace-id is consistent across all transactions and spans which belong to the same logical trace,
even for transactions and spans which happened in another service (given this service is also monitored by Elastic APM).

If this span represents a noop,
this method returns an empty string.

[float]
[[api-ensure-parent-id]]
==== `String ensureParentId()`
If the transaction does not have a parent-ID yet,
calling this method generates a new ID,
sets it as the parent-ID of this transaction,
and returns it as a `String`.

This enables the correlation of the spans the JavaScript Real User Monitoring (RUM) agent creates for the initial page load
with the transaction of the backend service.
If your backend service generates the HTML page dynamically,
initializing the JavaScript RUM agent with the value of this method allows analyzing the time spent in the browser vs in the backend services.

To enable the JavaScript RUM agent when using an HTML templating language like Freemarker,
add `ElasticApm.currentTransaction()` with the key `"transaction"` to the model.

Also, add a snippet similar to this to the body of your HTML page,
preferably before other JS libraries:

[source,html]
----
<script src="elastic-apm-js-base/dist/bundles/elastic-apm-js-base.umd.min.js"></script>
<script>
var elasticApm = initApm({
serviceName: '',
serverUrl: 'http://localhost:8200',
pageLoadTraceId: "${transaction.traceId}",
pageLoadSpanId: "${transaction.ensureParentId()}",
pageLoadSampled: ${transaction.sampled}
})
</script>
----

See the {apm-rum-ref}[JavaScript RUM agent documentation] for more information.

[float]
[[api-transaction-start-span]]
==== `Span createSpan()`
Expand Down Expand Up @@ -364,6 +410,17 @@ Returns the id of this span (never `null`)
If this span represents a noop,
this method returns an empty string.

[float]
[[api-span-get-trace-id]]
==== `String getTraceId()`
Returns the trace-ID of this span.

The trace-ID is consistent across all transactions and spans which belong to the same logical trace,
even for transactions and spans which happened in another service (given this service is also monitored by Elastic APM).

If this span represents a noop,
this method returns an empty string.

[float]
[[api-span-end]]
==== `void end()`
Expand Down