From 141a99bebc03096bfe443fb0a01cc982bae7d99f Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:30:39 +0100 Subject: [PATCH] update upstream 2.10.0 (#455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Update upstream OpenTelemetry agent version and related depend... ... encies Made with ❤️️ by updatecli * remove obsolete code * update span stacktrace + test feature works * remove obsolete code * update agent auto-config * remove obsolete test * fix another test * post-review changes --------- Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- common/build.gradle.kts | 3 - .../co/elastic/otel/common/SpanValue.java | 12 +- ...ingSpanProcessorAutoConfigurationTest.java | 7 +- .../co/elastic/otel/common/SpanValueTest.java | 9 +- .../co/elastic/otel/ElasticAgentListener.java | 41 --- ...icAutoConfigurationCustomizerProvider.java | 48 ++- .../elastic/otel/ElasticBreakdownMetrics.java | 301 ------------------ .../co/elastic/otel/ElasticExtension.java | 56 ---- .../co/elastic/otel/ElasticSpanProcessor.java | 64 ---- .../co/elastic/otel/SpanStackTraceFilter.java | 32 ++ .../SpanStackTraceProcessorAutoConfig.java | 59 ---- ...oConfigurationCustomizerProviderTest.java} | 0 .../otel/SpanStackTraceFilterTest.java | 53 +++ ...onfigTest.java => SpanStackTraceTest.java} | 107 ++++--- gradle/libs.versions.toml | 6 +- 15 files changed, 203 insertions(+), 595 deletions(-) delete mode 100644 custom/src/main/java/co/elastic/otel/ElasticAgentListener.java delete mode 100644 custom/src/main/java/co/elastic/otel/ElasticBreakdownMetrics.java delete mode 100644 custom/src/main/java/co/elastic/otel/ElasticExtension.java delete mode 100644 custom/src/main/java/co/elastic/otel/ElasticSpanProcessor.java create mode 100644 custom/src/main/java/co/elastic/otel/SpanStackTraceFilter.java delete mode 100644 custom/src/main/java/co/elastic/otel/SpanStackTraceProcessorAutoConfig.java rename custom/src/test/java/co/elastic/otel/{ElasticAutoConfigurationCustomizerproviderTest.java => ElasticAutoConfigurationCustomizerProviderTest.java} (100%) create mode 100644 custom/src/test/java/co/elastic/otel/SpanStackTraceFilterTest.java rename custom/src/test/java/co/elastic/otel/{SpanStackTraceProcessorAutoConfigTest.java => SpanStackTraceTest.java} (51%) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index f32a8a56..396698ba 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -12,9 +12,6 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") implementation(libs.bundles.semconv) - compileOnly(libs.contribSpanStacktrace) - testImplementation(libs.contribSpanStacktrace) - testImplementation("io.opentelemetry:opentelemetry-sdk") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") diff --git a/common/src/main/java/co/elastic/otel/common/SpanValue.java b/common/src/main/java/co/elastic/otel/common/SpanValue.java index 63e3b9d7..75fff21b 100644 --- a/common/src/main/java/co/elastic/otel/common/SpanValue.java +++ b/common/src/main/java/co/elastic/otel/common/SpanValue.java @@ -74,7 +74,6 @@ Its size is the number of dense SpanValues plus one (for the Map at index 0). */ private static final Class SDK_SPAN_CLASS = getSdkSpanClass(); - private static final Class CONTRIB_MUTABLE_SPAN_CLASS = getContribMutableSpanClass(); private static final SpanValueStorageProvider storageProvider = SpanValueStorageProvider.get(); @@ -261,7 +260,7 @@ private static SpanValueStorage getStorage(Object span, boolean initialize) { return storageProvider.get(unwrapped, initialize); } - /** Provides the underlying {@link SdkSpan} instance in case the given span is wrapped. */ + /** Provides the underlying {@code SdkSpan} instance in case the given span is wrapped. */ private static Span unwrap(Object span) { if (span.getClass() == SDK_SPAN_CLASS) { if (!((Span) span).getSpanContext().isValid()) { @@ -272,9 +271,6 @@ private static Span unwrap(Object span) { if (span instanceof MutableSpan) { return unwrap(((MutableSpan) span).getOriginalSpan()); } - if (CONTRIB_MUTABLE_SPAN_CLASS != null && CONTRIB_MUTABLE_SPAN_CLASS.isInstance(span)) { - return unwrap(ContribMutableSpanAccessor.getOriginalSpan(span)); - } if (span instanceof Span && !((Span) span).getSpanContext().isValid()) { throw new IllegalArgumentException("SpanValues don't work with invalid spans!"); } @@ -298,10 +294,4 @@ private static Class getContribMutableSpanClass() { return null; } } - - private static class ContribMutableSpanAccessor { - public static ReadableSpan getOriginalSpan(Object span) { - return ((io.opentelemetry.contrib.stacktrace.internal.MutableSpan) span).getOriginalSpan(); - } - } } diff --git a/common/src/test/java/co/elastic/otel/common/ChainingSpanProcessorAutoConfigurationTest.java b/common/src/test/java/co/elastic/otel/common/ChainingSpanProcessorAutoConfigurationTest.java index fdbf081a..31761c58 100644 --- a/common/src/test/java/co/elastic/otel/common/ChainingSpanProcessorAutoConfigurationTest.java +++ b/common/src/test/java/co/elastic/otel/common/ChainingSpanProcessorAutoConfigurationTest.java @@ -106,9 +106,10 @@ protected boolean requiresStart() { List spanProcessors = OtelReflectionUtils.getSpanProcessors(otel); assertThat(spanProcessors) - .containsExactlyInAnyOrder( - chainingProcessor.get(), SpanProcessor.composite() // NOOP-processor - ); + .hasSize(2) + .anySatisfy(proc -> assertThat(proc).isEqualTo(chainingProcessor.get())) + // NOOP-processor + .anySatisfy(proc -> assertThat(proc).isEqualTo(SpanProcessor.composite())); SpanProcessor terminal = chainingProcessor.get().next; assertThat(terminal).isInstanceOf(MutableCompositeSpanProcessor.class); diff --git a/common/src/test/java/co/elastic/otel/common/SpanValueTest.java b/common/src/test/java/co/elastic/otel/common/SpanValueTest.java index 440d819e..6d692b28 100644 --- a/common/src/test/java/co/elastic/otel/common/SpanValueTest.java +++ b/common/src/test/java/co/elastic/otel/common/SpanValueTest.java @@ -118,14 +118,7 @@ public static Stream testArgs() { "MutableSpan, sparse SpanValue", ValueAccess.create( SpanValue.createSparse(), - MutableSpan.makeMutable((ReadableSpan) newSpan())))), - Arguments.of( - Named.of( - "Contrib MutableSpan, sparse SpanValue", - ValueAccess.create( - SpanValue.createSparse(), - io.opentelemetry.contrib.stacktrace.internal.MutableSpan.makeMutable( - (ReadableSpan) newSpan()))))); + MutableSpan.makeMutable((ReadableSpan) newSpan()))))); } @ParameterizedTest diff --git a/custom/src/main/java/co/elastic/otel/ElasticAgentListener.java b/custom/src/main/java/co/elastic/otel/ElasticAgentListener.java deleted file mode 100644 index 340e2444..00000000 --- a/custom/src/main/java/co/elastic/otel/ElasticAgentListener.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.otel; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.javaagent.extension.AgentListener; -import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; - -// @AutoService(AgentListener.class) -@Deprecated -public class ElasticAgentListener implements AgentListener { - @Override - public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { - // We have to use an AgentListener in order to properly access the global OpenTelemetry instance - // trying to do this elsewhere can make attempting to call GlobalOpenTelemetry.set() more than - // once. - // - // Implementing this interface currently requires to add an explicit dependency to - // 'opentelemetry-sdk-extension-autoconfigure' as it is not provided for now. - OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - - ElasticExtension.INSTANCE.registerOpenTelemetry(openTelemetry); - } -} diff --git a/custom/src/main/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProvider.java b/custom/src/main/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProvider.java index 91037758..fd5d79a0 100644 --- a/custom/src/main/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProvider.java +++ b/custom/src/main/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProvider.java @@ -35,6 +35,16 @@ public class ElasticAutoConfigurationCustomizerProvider private static final String RUNTIME_EXPERIMENTAL_TELEMETRY = "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry"; + // must match value in io.opentelemetry.contrib.stacktrace.StackTraceAutoConfig + private static final String STACKTRACE_OTEL_FILTER = + "otel.java.experimental.span-stacktrace.filter"; + static final String STACKTRACE_OTEL_DURATION = + "otel.java.experimental.span-stacktrace.min.duration"; + static final String STACKTRACE_LEGACY1_DURATION = + "elastic.otel.java.span-stacktrace.min.duration"; + static final String STACKTRACE_LEGACY2_DURATION = + "elastic.otel.java.span.stacktrace.min.duration"; + @Override public void customize(AutoConfigurationCustomizer autoConfiguration) { autoConfiguration.addPropertiesCustomizer( @@ -42,22 +52,46 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { } static Map propertiesCustomizer(ConfigProperties configProperties) { - Set disabledResourceProviders = - new HashSet<>(configProperties.getList(DISABLED_RESOURCE_PROVIDERS)); + Map config = new HashMap<>(); - // disable upstream distro name & version provider - disabledResourceProviders.add( - "io.opentelemetry.javaagent.tooling.DistroVersionResourceProvider"); + experimentalTelemetry(config, configProperties); + resourceProviders(config, configProperties); + spanStackTrace(config, configProperties); - Map config = new HashMap<>(); + return config; + } + private static void experimentalTelemetry( + Map config, ConfigProperties configProperties) { // enable experimental telemetry metrics by default if not explicitly disabled boolean experimentalTelemetry = configProperties.getBoolean(RUNTIME_EXPERIMENTAL_TELEMETRY, true); config.put(RUNTIME_EXPERIMENTAL_TELEMETRY, Boolean.toString(experimentalTelemetry)); + } + private static void resourceProviders( + Map config, ConfigProperties configProperties) { + Set disabledResourceProviders = + new HashSet<>(configProperties.getList(DISABLED_RESOURCE_PROVIDERS)); + + // disable upstream distro name & version provider + disabledResourceProviders.add( + "io.opentelemetry.javaagent.tooling.DistroVersionResourceProvider"); config.put(DISABLED_RESOURCE_PROVIDERS, String.join(",", disabledResourceProviders)); + } - return config; + private static void spanStackTrace( + Map config, ConfigProperties configProperties) { + + String value = configProperties.getString(STACKTRACE_OTEL_DURATION); + if (value == null) { + value = configProperties.getString(STACKTRACE_LEGACY1_DURATION); + if (value == null) { + value = configProperties.getString(STACKTRACE_LEGACY2_DURATION); + } + config.put(STACKTRACE_OTEL_DURATION, value); + } + + config.put(STACKTRACE_OTEL_FILTER, SpanStackTraceFilter.class.getName()); } } diff --git a/custom/src/main/java/co/elastic/otel/ElasticBreakdownMetrics.java b/custom/src/main/java/co/elastic/otel/ElasticBreakdownMetrics.java deleted file mode 100644 index b72afc17..00000000 --- a/custom/src/main/java/co/elastic/otel/ElasticBreakdownMetrics.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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.otel; - -import co.elastic.otel.common.ElasticAttributes; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.AttributeType; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.common.Clock; -import io.opentelemetry.sdk.trace.ReadWriteSpan; -import io.opentelemetry.sdk.trace.ReadableSpan; -import io.opentelemetry.sdk.trace.data.SpanData; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class ElasticBreakdownMetrics { - - private static final Logger logger = Logger.getLogger(ElasticBreakdownMetrics.class.getName()); - - private final ConcurrentHashMap elasticSpanData; - - private ElasticSpanExporter spanExporter; - - private LongCounter breakDownCounter; - - // sidecar object we store for every span - private static class BreakdownData { - - private ReadableSpan localRoot; - - private final ChildDuration childDuration; - private long selfTime; - - public BreakdownData(ReadableSpan localRoot, long start) { - this.localRoot = localRoot; - this.childDuration = new ChildDuration(start); - this.selfTime = Long.MIN_VALUE; - } - - public long getSelfTime() { - if (selfTime < 0) { - throw new IllegalStateException("invalid self time"); - } - return selfTime; - } - - private void setSelfTime(long selfTime) { - this.selfTime = selfTime; - } - - public String getLocalRootSpanType() { - // use a dummy heuristic that matches current transaction.type - return "request"; - } - - public String getLocalRootSpanName() { - return localRoot.getName(); - } - } - - public ElasticBreakdownMetrics() { - this.elasticSpanData = (ConcurrentHashMap) new ConcurrentHashMap(); - } - - public void registerOpenTelemetry(OpenTelemetry openTelemetry) { - Meter meter = openTelemetry.getMeterProvider().meterBuilder("elastic.span_breakdown").build(); - breakDownCounter = meter.counterBuilder("elastic.span_breakdown").build(); - } - - public void onSpanStart(Context parentContext, ReadWriteSpan span) { - // unfortunately we can't cast to SdkSpan as it's loaded in AgentClassloader and the extension - // is loaded - // in the ExtensionClassloader, hence we can't use the package-protected - // SdkSpan#getStartEpochNanos method - // - // However, adding accessors to the start/and timestamps to the ReadableSpan interface could be - // something we - // could attempt to contribute. - long spanStart = span.toSpanData().getStartEpochNanos(); - - SpanContext spanContext = span.getSpanContext(); - SpanContext localRootSpanContext; - - // We can use this because we have a read write span here - // alternatively we could have resolved the parent span on the parent context to get parent span - // context. - if (isRootSpanParent(span.getParentSpanContext())) { - // the span is a local root span - localRootSpanContext = spanContext; - - elasticSpanData.put(spanContext, new BreakdownData(span, spanStart)); - - } else { - ReadableSpan parentSpan = getReadableSpanFromContext(parentContext); - // retrieve and store the local root span for later use - ReadableSpan localRoot = lookupLocalRootSpan(parentSpan); - if (localRoot != null) { - localRootSpanContext = localRoot.getSpanContext(); - if (localRootSpanContext.isValid()) { - elasticSpanData.put(spanContext, new BreakdownData(localRoot, spanStart)); - } - } else { - localRootSpanContext = Span.getInvalid().getSpanContext(); - } - - // update direct parent span child durations for self-time - BreakdownData breakdownData = elasticSpanData.get(span.getParentSpanContext()); - if (breakdownData != null) { - ChildDuration parentChildDuration = breakdownData.childDuration; - parentChildDuration.startChild(spanStart); - } - - logger.log( - Level.FINER, - String.format( - "start of child span %s, parent = %s, root = %s", - spanContext.getSpanId(), - span.getParentSpanContext().getSpanId(), - localRootSpanContext.getSpanId())); - } - // we store extra attributes in span for later use, however we can't replace because we - // don't - // have access to the - // attributes of the parent span, only its span context or the write-only Span - span.setAttribute(ElasticAttributes.IS_LOCAL_ROOT, localRootSpanContext == spanContext); - span.setAttribute(ElasticAttributes.LOCAL_ROOT_ID, localRootSpanContext.getSpanId()); - } - - private static ReadableSpan getReadableSpanFromContext(Context context) { - Span span = Span.fromContextOrNull(context); - return (span instanceof ReadableSpan) ? (ReadableSpan) span : null; - } - - public void onSpanEnd(ReadableSpan span) { - - SpanContext spanContext = span.getSpanContext(); - - // retrieve local root span from storage - ReadableSpan localRoot = lookupLocalRootSpan(span); - - SpanData spanData = span.toSpanData(); - - // children duration for current span - BreakdownData spanContextData = elasticSpanData.get(spanContext); - - // update children duration for direct parent - BreakdownData parentSpanContextData = elasticSpanData.get(span.getParentSpanContext()); - - if (parentSpanContextData != null) { // parent might be already terminated - parentSpanContextData.childDuration.endChild(spanData.getEndEpochNanos()); - } - - if (spanContextData != null) { - - long selfTime = spanContextData.childDuration.endSpan(spanData.getEndEpochNanos()); - - AttributesBuilder metricAttributes = - buildCounterAttributes(spanData.getAttributes()) - .put(ElasticAttributes.LOCAL_ROOT_TYPE, spanContextData.getLocalRootSpanType()) - .put(ElasticAttributes.LOCAL_ROOT_NAME, spanContextData.getLocalRootSpanName()) - // put measured metric as span attribute to allow using an ingest pipeline to alter - // storage ingest pipelines do not have access to _source and thus can't read the - // metric as-is. - // - // (ab)using metric attributes for this breaks due to high cardinality - // see https://github.com/elastic/elastic-otel-java/issues/383 for details - // .put(ElasticAttributes.SELF_TIME, selfTime) - ; - - // unfortunately here we get a read-only span that has already been ended, thus even a cast to - // ReadWriteSpan - // does not allow us from adding extra span attributes - if (spanExporter != null) { - spanContextData.setSelfTime(selfTime); - spanExporter.addAttribute( - spanContext, ElasticAttributes.SELF_TIME, spanContextData.getSelfTime()); - } - - breakDownCounter.add(selfTime, metricAttributes.build()); - elasticSpanData.remove(spanContext); - } - } - - private static AttributesBuilder buildCounterAttributes(Attributes spanAttributes) { - AttributesBuilder builder = - Attributes.builder() - // default to app/internal unless other span attributes - .put(ElasticAttributes.SPAN_TYPE, "app") - .put(ElasticAttributes.SPAN_SUBTYPE, "internal"); - - spanAttributes.forEach( - (k, v) -> { - String key = k.getKey(); - if (AttributeType.STRING.equals(k.getType())) { - int index = key.indexOf(".system"); - if (index > 0) { - builder.put(ElasticAttributes.SPAN_TYPE, key.substring(0, index)); - builder.put(ElasticAttributes.SPAN_SUBTYPE, v.toString()); - } - } - }); - return builder; - } - - private ReadableSpan lookupLocalRootSpan(ReadableSpan span) { - if (span == null) { - return null; - } - BreakdownData spanContextData = elasticSpanData.get(span.getSpanContext()); - return spanContextData != null ? spanContextData.localRoot : null; - } - - private static boolean isRootSpanParent(SpanContext parentSpanContext) { - return !parentSpanContext.isValid() || parentSpanContext.isRemote(); - } - - public void registerSpanExporter(ElasticSpanExporter spanExporter) { - this.spanExporter = spanExporter; - } - - private static class ChildDuration { - - public static final Clock clock = Clock.getDefault(); - - private final AtomicInteger activeChildren; - - private final long startEpochNanos; - - // timestamp of the 1st child start - private long childStartEpoch; - - // duration for which there was at least child span executing - private long childDuration; - - public ChildDuration(long startEpochNanos) { - this.activeChildren = new AtomicInteger(); - this.startEpochNanos = startEpochNanos; - } - - public void startChild(long startEpochNanos) { - int count; - synchronized (this) { - count = activeChildren.incrementAndGet(); - if (count == 1) { - childStartEpoch = startEpochNanos; - } - } - - logger.log(Level.FINER, String.format("start child span, count = %d", count)); - } - - public void endChild(long endEpochNanos) { - - int count; - synchronized (this) { - count = activeChildren.decrementAndGet(); - if (count == 0) { - childDuration += (endEpochNanos - childStartEpoch); - childStartEpoch = -1L; - } - } - } - - /** - * @param endEpochNanos span end timestamp - * @return span self time - */ - public long endSpan(long endEpochNanos) { - synchronized (this) { - if (childStartEpoch > 0) { - childDuration += (clock.now() - childStartEpoch); - } - } - return endEpochNanos - startEpochNanos - childDuration; - } - } -} diff --git a/custom/src/main/java/co/elastic/otel/ElasticExtension.java b/custom/src/main/java/co/elastic/otel/ElasticExtension.java deleted file mode 100644 index ee439cc6..00000000 --- a/custom/src/main/java/co/elastic/otel/ElasticExtension.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.otel; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.trace.SpanProcessor; -import io.opentelemetry.sdk.trace.export.SpanExporter; - -/** - * Deprecated as breakdown metrics is experimental and has at least one issue - */ -@Deprecated -public class ElasticExtension { - - public static final ElasticExtension INSTANCE = new ElasticExtension(); - private final ElasticBreakdownMetrics breakdownMetrics; - private final ElasticSpanProcessor spanProcessor; - private ElasticSpanExporter spanExporter; - - private ElasticExtension() { - this.breakdownMetrics = new ElasticBreakdownMetrics(); - this.spanProcessor = new ElasticSpanProcessor(breakdownMetrics); - } - - public void registerOpenTelemetry(OpenTelemetry openTelemetry) { - breakdownMetrics.registerOpenTelemetry(openTelemetry); - } - - public SpanProcessor getSpanProcessor() { - return spanProcessor; - } - - public SpanExporter wrapSpanExporter(SpanExporter toWrap) { - spanExporter = new ElasticSpanExporter(toWrap); - breakdownMetrics.registerSpanExporter(spanExporter); - spanProcessor.registerSpanExporter(spanExporter); - return spanExporter; - } -} diff --git a/custom/src/main/java/co/elastic/otel/ElasticSpanProcessor.java b/custom/src/main/java/co/elastic/otel/ElasticSpanProcessor.java deleted file mode 100644 index e195add4..00000000 --- a/custom/src/main/java/co/elastic/otel/ElasticSpanProcessor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.otel; - -import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.trace.ReadWriteSpan; -import io.opentelemetry.sdk.trace.ReadableSpan; -import io.opentelemetry.sdk.trace.SpanProcessor; - -public class ElasticSpanProcessor implements SpanProcessor { - - private final ElasticBreakdownMetrics breakdownMetrics; - private ElasticSpanExporter spanExporter; - - public ElasticSpanProcessor(ElasticBreakdownMetrics breakdownMetrics) { - this.breakdownMetrics = breakdownMetrics; - } - - @Override - public void onStart(Context parentContext, ReadWriteSpan span) { - breakdownMetrics.onSpanStart(parentContext, span); - } - - @Override - public boolean isStartRequired() { - return true; - } - - @Override - public void onEnd(ReadableSpan span) { - breakdownMetrics.onSpanEnd(span); - } - - @Override - public boolean isEndRequired() { - return true; - } - - @Override - public CompletableResultCode shutdown() { - return CompletableResultCode.ofSuccess(); - } - - public void registerSpanExporter(ElasticSpanExporter spanExporter) { - this.spanExporter = spanExporter; - } -} diff --git a/custom/src/main/java/co/elastic/otel/SpanStackTraceFilter.java b/custom/src/main/java/co/elastic/otel/SpanStackTraceFilter.java new file mode 100644 index 00000000..8ed5ab5b --- /dev/null +++ b/custom/src/main/java/co/elastic/otel/SpanStackTraceFilter.java @@ -0,0 +1,32 @@ +/* + * 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.otel; + +import co.elastic.otel.common.ElasticAttributes; +import io.opentelemetry.sdk.trace.ReadableSpan; +import java.util.function.Predicate; + +public class SpanStackTraceFilter implements Predicate { + + @Override + public boolean test(ReadableSpan readableSpan) { + Boolean inferred = readableSpan.getAttribute(ElasticAttributes.IS_INFERRED); + return !Boolean.TRUE.equals(inferred); + } +} diff --git a/custom/src/main/java/co/elastic/otel/SpanStackTraceProcessorAutoConfig.java b/custom/src/main/java/co/elastic/otel/SpanStackTraceProcessorAutoConfig.java deleted file mode 100644 index 0ac77436..00000000 --- a/custom/src/main/java/co/elastic/otel/SpanStackTraceProcessorAutoConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.otel; - -import co.elastic.otel.common.ChainingSpanProcessorAutoConfiguration; -import co.elastic.otel.common.ChainingSpanProcessorRegisterer; -import co.elastic.otel.common.ElasticAttributes; -import com.google.auto.service.AutoService; -import io.opentelemetry.contrib.stacktrace.StackTraceSpanProcessor; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import java.time.Duration; - -@AutoService(ChainingSpanProcessorAutoConfiguration.class) -public class SpanStackTraceProcessorAutoConfig implements ChainingSpanProcessorAutoConfiguration { - - static final String LEGACY_DURATION_CONFIG_OPTION = - "elastic.otel.java.span-stacktrace.min.duration"; - // TODO replace this with upstream config once it's stable - static final String MIN_DURATION_CONFIG_OPTION = "elastic.otel.java.span.stacktrace.min.duration"; - - @Override - public void registerSpanProcessors( - ConfigProperties properties, ChainingSpanProcessorRegisterer registerer) { - - Duration legacyMinDuration = - properties.getDuration(LEGACY_DURATION_CONFIG_OPTION, Duration.ofMillis(5)); - Duration minDuration = properties.getDuration(MIN_DURATION_CONFIG_OPTION, legacyMinDuration); - if (minDuration.isNegative()) { - return; - } - registerer.register( - next -> - new StackTraceSpanProcessor( - next, - minDuration.toNanos(), - span -> { - // Do not add a stacktrace for inferred spans: If a good one was available - // it would have been added by the module creating this span - return !Boolean.TRUE.equals(span.getAttribute(ElasticAttributes.IS_INFERRED)); - }), - ChainingSpanProcessorRegisterer.ORDER_FIRST); - } -} diff --git a/custom/src/test/java/co/elastic/otel/ElasticAutoConfigurationCustomizerproviderTest.java b/custom/src/test/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProviderTest.java similarity index 100% rename from custom/src/test/java/co/elastic/otel/ElasticAutoConfigurationCustomizerproviderTest.java rename to custom/src/test/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProviderTest.java diff --git a/custom/src/test/java/co/elastic/otel/SpanStackTraceFilterTest.java b/custom/src/test/java/co/elastic/otel/SpanStackTraceFilterTest.java new file mode 100644 index 00000000..d132d049 --- /dev/null +++ b/custom/src/test/java/co/elastic/otel/SpanStackTraceFilterTest.java @@ -0,0 +1,53 @@ +/* + * 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.otel; + +import static org.assertj.core.api.Assertions.assertThat; + +import co.elastic.otel.common.ElasticAttributes; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.ReadableSpan; +import org.junit.jupiter.api.Test; + +class SpanStackTraceFilterTest { + + @Test + void filtering() { + Tracer tracer = + OpenTelemetrySdk.builder().build().getTracerProvider().tracerBuilder("test").build(); + SpanStackTraceFilter filter = new SpanStackTraceFilter(); + + ReadableSpan simpleSpan = (ReadableSpan) tracer.spanBuilder("span").startSpan(); + assertThat(filter.test(simpleSpan)).isTrue(); + + SpanBuilder spanNotInferred = + tracer.spanBuilder("span").setAttribute(ElasticAttributes.IS_INFERRED, Boolean.FALSE); + assertThat(filter.test((ReadableSpan) spanNotInferred.startSpan())).isTrue(); + + ReadableSpan inferredSpan = + (ReadableSpan) + tracer + .spanBuilder("span") + .setAttribute(ElasticAttributes.IS_INFERRED, Boolean.TRUE) + .startSpan(); + assertThat(filter.test(inferredSpan)).describedAs("inferred spans must be filtered").isFalse(); + } +} diff --git a/custom/src/test/java/co/elastic/otel/SpanStackTraceProcessorAutoConfigTest.java b/custom/src/test/java/co/elastic/otel/SpanStackTraceTest.java similarity index 51% rename from custom/src/test/java/co/elastic/otel/SpanStackTraceProcessorAutoConfigTest.java rename to custom/src/test/java/co/elastic/otel/SpanStackTraceTest.java index d07f7446..27b2396b 100644 --- a/custom/src/test/java/co/elastic/otel/SpanStackTraceProcessorAutoConfigTest.java +++ b/custom/src/test/java/co/elastic/otel/SpanStackTraceTest.java @@ -21,6 +21,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import co.elastic.otel.common.ElasticAttributes; import co.elastic.otel.testing.AutoConfigTestProperties; import co.elastic.otel.testing.AutoConfiguredDataCapture; import co.elastic.otel.testing.OtelReflectionUtils; @@ -31,11 +32,22 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; import java.util.List; +import org.assertj.core.api.AbstractCharSequenceAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Tests Span stack trace capture feature is properly behaving, which is mostly ensuring that the + * contrib implementation is properly configured for usage in this distribution. + */ +public class SpanStackTraceTest { + + private static final String OTEL_MIN_DURATION = + "otel.java.experimental.span-stacktrace.min.duration"; -public class SpanStackTraceProcessorAutoConfigTest { @BeforeEach @AfterEach void resetGlobalOtel() { @@ -45,32 +57,27 @@ void resetGlobalOtel() { @Test void checkStackTracePresent() { try (AutoConfigTestProperties testProps = - new AutoConfigTestProperties() - .put(SpanStackTraceProcessorAutoConfig.MIN_DURATION_CONFIG_OPTION, "0ms")) { + new AutoConfigTestProperties().put(OTEL_MIN_DURATION, "0ms")) { OpenTelemetry otel = GlobalOpenTelemetry.get(); - // TODO: cleanup other extensions (Breakdown) so that this is not required: - ElasticExtension.INSTANCE.registerOpenTelemetry(otel); - Tracer tracer = otel.getTracer("test-tracer"); tracer.spanBuilder("my-span").startSpan().end(); - List spans = AutoConfiguredDataCapture.getSpans(); - - assertThat(spans).hasSize(1); - assertThat(spans.get(0)) - .hasName("my-span") - .hasAttribute( - satisfies(CodeIncubatingAttributes.CODE_STACKTRACE, att -> att.isNotBlank())); + checkSpanStackTrace(true); } } - @Test - void featureCanBeDisabled() { + @ParameterizedTest + @ValueSource( + strings = { + ElasticAutoConfigurationCustomizerProvider.STACKTRACE_OTEL_DURATION, + ElasticAutoConfigurationCustomizerProvider.STACKTRACE_LEGACY1_DURATION, + ElasticAutoConfigurationCustomizerProvider.STACKTRACE_LEGACY2_DURATION + }) + void featureCanBeDisabled(String configName) { try (AutoConfigTestProperties testProps = - new AutoConfigTestProperties() - .put(SpanStackTraceProcessorAutoConfig.MIN_DURATION_CONFIG_OPTION, "-1")) { + new AutoConfigTestProperties().put(configName, "-1")) { OpenTelemetry otel = GlobalOpenTelemetry.get(); assertThat(OtelReflectionUtils.getSpanProcessors(otel)) @@ -78,48 +85,70 @@ void featureCanBeDisabled() { } } - @Test - void legacyConfigOptionSupported() { + @ParameterizedTest + @ValueSource( + strings = { + ElasticAutoConfigurationCustomizerProvider.STACKTRACE_OTEL_DURATION, + ElasticAutoConfigurationCustomizerProvider.STACKTRACE_LEGACY1_DURATION, + ElasticAutoConfigurationCustomizerProvider.STACKTRACE_LEGACY2_DURATION + }) + void legacyConfigOptionsSupported(String configName) { try (AutoConfigTestProperties testProps = - new AutoConfigTestProperties() - .put(SpanStackTraceProcessorAutoConfig.LEGACY_DURATION_CONFIG_OPTION, "0ms")) { + new AutoConfigTestProperties().put(configName, "0ms")) { OpenTelemetry otel = GlobalOpenTelemetry.get(); - // TODO: cleanup other extensions (Breakdown) so that this is not required: - ElasticExtension.INSTANCE.registerOpenTelemetry(otel); + Tracer tracer = otel.getTracer("test-tracer"); + + tracer.spanBuilder("my-span").startSpan().end(); + + checkSpanStackTrace(true); + } + } + + @Test + void checkMinDurationRespected() { + try (AutoConfigTestProperties testProps = + new AutoConfigTestProperties().put(OTEL_MIN_DURATION, "100s")) { + OpenTelemetry otel = GlobalOpenTelemetry.get(); Tracer tracer = otel.getTracer("test-tracer"); tracer.spanBuilder("my-span").startSpan().end(); - List spans = AutoConfiguredDataCapture.getSpans(); + checkSpanStackTrace(false); + } + } - assertThat(spans).hasSize(1); - assertThat(spans.get(0)) - .hasName("my-span") + private void checkSpanStackTrace(boolean stackTraceExpected) { + List spans = AutoConfiguredDataCapture.getSpans(); + assertThat(spans).hasSize(1); + SpanData span = spans.get(0); + if (stackTraceExpected) { + assertThat(span) .hasAttribute( - satisfies(CodeIncubatingAttributes.CODE_STACKTRACE, att -> att.isNotBlank())); + satisfies( + CodeIncubatingAttributes.CODE_STACKTRACE, + AbstractCharSequenceAssert::isNotBlank)); + } else { + assertThat(span.getAttributes().get(CodeIncubatingAttributes.CODE_STACKTRACE)).isNull(); } } @Test - void checkMinDurationRespected() { + void checkInferredSpansIgnored() { try (AutoConfigTestProperties testProps = - new AutoConfigTestProperties() - .put(SpanStackTraceProcessorAutoConfig.MIN_DURATION_CONFIG_OPTION, "100s")) { + new AutoConfigTestProperties().put(OTEL_MIN_DURATION, "0")) { OpenTelemetry otel = GlobalOpenTelemetry.get(); - // TODO: cleanup other extensions (Breakdown) so that this is not required: - ElasticExtension.INSTANCE.registerOpenTelemetry(otel); - Tracer tracer = otel.getTracer("test-tracer"); - tracer.spanBuilder("my-span").startSpan().end(); + tracer + .spanBuilder("my-span") + .setAttribute(ElasticAttributes.IS_INFERRED, true) + .startSpan() + .end(); - List spans = AutoConfiguredDataCapture.getSpans(); - assertThat(spans).hasSize(1); - assertThat(spans.get(0).getAttributes().get(CodeIncubatingAttributes.CODE_STACKTRACE)) - .isNull(); + checkSpanStackTrace(false); } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd2a3e7f..df344a23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,17 +10,17 @@ opentelemetryProto = "1.3.2-alpha" # otel agent, we rely on the '*-alpha' and get the non-alpha dependencies transitively # updated from upstream agent with .ci/update-upstream.sh -opentelemetryJavaagentAlpha = "2.9.0-alpha" +opentelemetryJavaagentAlpha = "2.10.0-alpha" # otel contrib # updated from upstream agent with .ci/update-upstream.sh -opentelemetryContribAlpha = "1.39.0-alpha" +opentelemetryContribAlpha = "1.40.0-alpha" # otel semconv # updated from upstream agent with .ci/update-upstream.sh # While the semconv stable artifact is provided transitively by the agent, we still have to explicitly # reference the "incubating" version explicitly -opentelemetrySemconvAlpha = "1.25.0-alpha" +opentelemetrySemconvAlpha = "1.28.0-alpha" [libraries]