Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,8 @@ protected void doSetDestinationAddress(@Nullable String address, int port) {
protected void doSetDestinationService(@Nullable String resource) {
// co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation.SetDestinationServiceInstrumentation
}

protected void doSetNonDiscardable() {
// co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation.SetNonDiscardableInstrumentation
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,10 @@ public Span setDestinationAddress(@Nullable String address, int port) {
public Span setDestinationService(@Nullable String resource) {
return this;
}

@Override
@Nonnull
public Span setNonDiscardable() {
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,10 @@ public Span setDestinationAddress(@Nullable String address, int port) {
public Span setDestinationService(@Nullable String resource) {
return this;
}

@Nonnull
@Override
public Span setNonDiscardable() {
return this;
}
}
13 changes: 13 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 @@ -466,4 +466,17 @@ public interface Span {
*/
@Nonnull
Span setDestinationService(@Nullable String resource);

/**
* Makes this span non-discardable.
* In some cases, spans may be discarded, for example if {@code span_min_duration} config option is set and the span does not exceed
* the configured threshold. Use this method to make sure the current span is not discarded.
*
* NOTE: making a span non-discardable implicitly makes the entire stack of active spans non-discardable as well. Child spans can still
* be discarded.
*
* @return this span
*/
@Nonnull
Span setNonDiscardable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,11 @@ public Span setDestinationService(@Nullable String resource) {
doSetDestinationService(resource);
return this;
}

@Nonnull
@Override
public Span setNonDiscardable() {
doSetNonDiscardable();
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,11 @@ public Transaction setDestinationAddress(@Nullable String address, int port) {
public Transaction setDestinationService(@Nullable String resource) {
throw new UnsupportedOperationException();
}

@Nonnull
@Override
public Span setNonDiscardable() {
doSetNonDiscardable();
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -468,4 +468,20 @@ public static void setDestinationService(@Advice.FieldValue(value = "span", typi
}
}
}

public static class SetNonDiscardableInstrumentation extends AbstractSpanInstrumentation {

public SetNonDiscardableInstrumentation() {
super(named("doSetNonDiscardable"));
}

public static class AdviceClass {
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static void setNonDiscardable(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Object context) {
if (context instanceof AbstractSpan<?>) {
((AbstractSpan<?>) context).setNonDiscardable();
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$IsSampledInstrumentat
co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$InjectTraceHeadersInstrumentation
co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$SetDestinationAddressInstrumentation
co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$SetDestinationServiceInstrumentation
co.elastic.apm.agent.pluginapi.AbstractSpanInstrumentation$SetNonDiscardableInstrumentation
co.elastic.apm.agent.pluginapi.CaptureExceptionInstrumentation
co.elastic.apm.agent.pluginapi.ApiScopeInstrumentation
co.elastic.apm.agent.pluginapi.CaptureTransactionInstrumentation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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.apm.api;

import co.elastic.apm.AbstractApiTest;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.configuration.converter.TimeDuration;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;

@SuppressWarnings("unused")
public class SpanDiscardingTest extends AbstractApiTest {

@Test
void testSpanDiscarding() {
doReturn(TimeDuration.of("100ms")).when(config.getConfig(CoreConfiguration.class)).getSpanMinDuration();
Transaction transaction = ElasticApm.startTransaction();
try (Scope activate = transaction.activate()) {
parent(true);
parent(false);
}
List<co.elastic.apm.agent.impl.transaction.Span> spans = reporter.getSpans();
assertThat(spans).hasSize(2);
assertThat(spans).anyMatch(span -> span.getNameAsString().equals("parent-false"));
assertThat(spans).anyMatch(span -> span.getNameAsString().equals("not-discarded"));
transaction.end();
}

private void parent(boolean discardChild) {
Span span = ElasticApm.currentSpan().startSpan().setName("parent-" + discardChild);
try (Scope activate = span.activate()) {
if (discardChild) {
discarded();
} else {
notDiscarded();
}
}
span.end();
}

private void discarded() {
Span span = ElasticApm.currentSpan().startSpan().setName("discarded");
try (Scope activate = span.activate()) {
child();
}
span.end();
}

private void notDiscarded() {
Span span = ElasticApm.currentSpan().startSpan().setName("not-discarded").setNonDiscardable();
try (Scope activate = span.activate()) {
child();
}
span.end();
}

private void child() {
ElasticApm.currentSpan().startSpan().setName("child").end();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.apm.agent.opentelemetry.sdk;

/**
* An extension to the OTel API that allows us to provide special tracing settings.
* Such settings may affect tracing behavior internally, but they are not added as data and not persisted as span attributes.
*/
public class BehavioralAttributes {
public static final String NON_DISCARDABLE = "Elastic-OpenTelemetry-NonDiscardable";
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ public OTelSpan(AbstractSpan<?> span) {

@Override
public <T> Span setAttribute(AttributeKey<T> key, @Nonnull T value) {
span.getOtelAttributes().put(key.getKey(), value);
boolean behavioralAttribute = false;
if (key.getKey().equals(BehavioralAttributes.NON_DISCARDABLE)) {
span.setNonDiscardable();
behavioralAttribute = true;
}
if (!behavioralAttribute) {
span.getOtelAttributes().put(key.getKey(), value);
}
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.apm.agent.opentelemetry.sdk;

import co.elastic.apm.agent.AbstractInstrumentationTest;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import org.junit.Before;

import static org.assertj.core.api.Assertions.assertThat;

public class AbstractOpenTelemetryTest extends AbstractInstrumentationTest {

protected OpenTelemetry openTelemetry;
protected Tracer otelTracer;

@Before
public void setUp() {
this.openTelemetry = GlobalOpenTelemetry.get();
assertThat(openTelemetry).isSameAs(GlobalOpenTelemetry.get());
otelTracer = openTelemetry.getTracer(null);

// otel spans are not recycled for now
disableRecyclingValidation();

// otel spans should have unknown outcome by default unless explicitly set through API
reporter.disableCheckUnknownOutcome();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@
*/
package co.elastic.apm.agent.opentelemetry.sdk;

import co.elastic.apm.agent.AbstractInstrumentationTest;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.ElasticContext;
import co.elastic.apm.agent.impl.transaction.OTelSpanKind;
import co.elastic.apm.agent.impl.transaction.Outcome;
import co.elastic.apm.agent.impl.transaction.Transaction;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.Scope;
Expand All @@ -42,32 +38,14 @@
import javax.annotation.Nullable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static org.assertj.core.api.Assertions.assertThat;

public class ElasticOpenTelemetryTest extends AbstractInstrumentationTest {

private OpenTelemetry openTelemetry;
private Tracer otelTracer;

@Before
public void setUp() {
this.openTelemetry = GlobalOpenTelemetry.get();
assertThat(openTelemetry).isSameAs(GlobalOpenTelemetry.get());
otelTracer = openTelemetry.getTracer(null);

// otel spans are not recycled for now
disableRecyclingValidation();

// otel spans should have unknown outcome by default unless explicitly set through API
reporter.disableCheckUnknownOutcome();
}
public class ElasticOpenTelemetryTest extends AbstractOpenTelemetryTest {

@Before
public void before() {
Expand Down
Loading