diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java index b2413620..017235ca 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java @@ -20,6 +20,7 @@ import com.solarwinds.joboe.config.ConfigProperty; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.trace.ReadableSpan; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -27,18 +28,29 @@ import java.util.stream.Collectors; public class SpanStacktraceFilter implements Predicate { - private Set filterAttributes = new HashSet<>(); + private static final String DEFAULT_SELECTOR_ATTRIBUTE = "db.system"; + + private Set selectorAttributes = new HashSet<>(); + + public SpanStacktraceFilter() {} + + public SpanStacktraceFilter(Collection configuredFilterAttributes) { + Set effectiveFilterAttributes = new HashSet<>(configuredFilterAttributes); + effectiveFilterAttributes.add(DEFAULT_SELECTOR_ATTRIBUTE); + selectorAttributes = effectiveFilterAttributes; + } @Override public boolean test(ReadableSpan readableSpan) { - if (filterAttributes.isEmpty()) { + if (selectorAttributes.isEmpty()) { Set configuredFilterAttributes = ConfigManager.getConfigOptional( - ConfigProperty.AGENT_SPAN_STACKTRACE_FILTERS, Collections.singleton("db.system")); + ConfigProperty.AGENT_SPAN_STACKTRACE_FILTERS, + Collections.singleton(DEFAULT_SELECTOR_ATTRIBUTE)); Set effectiveFilterAttributes = new HashSet<>(configuredFilterAttributes); - effectiveFilterAttributes.add("db.system"); - filterAttributes = effectiveFilterAttributes; + effectiveFilterAttributes.add(DEFAULT_SELECTOR_ATTRIBUTE); + selectorAttributes = effectiveFilterAttributes; } Set attributes = @@ -46,6 +58,6 @@ public boolean test(ReadableSpan readableSpan) { .map(AttributeKey::getKey) .collect(Collectors.toSet()); - return filterAttributes.stream().anyMatch(attributes::contains); + return selectorAttributes.stream().anyMatch(attributes::contains); } } diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/StacktraceFilterParser.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/StacktraceFilterParser.java deleted file mode 100644 index 01529e21..00000000 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/StacktraceFilterParser.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * Licensed 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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import com.google.auto.service.AutoService; -import com.solarwinds.joboe.config.InvalidConfigException; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@AutoService(DeclarativeConfigPropertyParser.class) -public final class StacktraceFilterParser implements DeclarativeConfigPropertyParser { - private static final String CONFIG_KEY = "agent.spanStacktraceFilters"; - - @Override - public Set convert(DeclarativeConfigProperties declarativeConfigProperties) - throws InvalidConfigException { - List spanAttributes = - declarativeConfigProperties.getScalarList( - CONFIG_KEY, String.class, Collections.emptyList()); - return new HashSet<>(spanAttributes); - } - - @Override - public String configKey() { - return CONFIG_KEY; - } -} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java index c51d4e65..cf082e01 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java @@ -16,8 +16,6 @@ package com.solarwinds.opentelemetry.extensions.config.provider; -import static com.solarwinds.opentelemetry.extensions.SharedNames.SPAN_STACKTRACE_FILTER_CLASS; - import com.google.auto.service.AutoService; import com.solarwinds.joboe.config.ConfigManager; import com.solarwinds.joboe.config.ConfigProperty; @@ -52,7 +50,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; @AutoService(DeclarativeConfigurationCustomizerProvider.class) public class SharedConfigCustomizerProvider implements DeclarativeConfigurationCustomizerProvider { @@ -88,7 +85,6 @@ public void customize(DeclarativeConfigurationCustomizer customizer) { addMetricExporter(configurationModel); if (tracerProvider != null) { addSampler(tracerProvider); - addProcessors(tracerProvider); addSpanExporter(configurationModel); } @@ -110,44 +106,6 @@ private static Map properties(Object... keyValues) { return properties; } - private void addProcessors(TracerProviderModel model) { - List spanProcessorModels = model.getProcessors(); - if (spanProcessorModels == null) { - spanProcessorModels = new ArrayList<>(); - } - - ArrayList allProcessors = new ArrayList<>(spanProcessorModels); - String experimentalStacktrace = "stacktrace/development"; - Optional modelOptional = - spanProcessorModels.stream() - .filter( - processorModel -> - processorModel.getExtensionProperties().containsKey(experimentalStacktrace)) - .findFirst(); - - if (!modelOptional.isPresent()) { - allProcessors.add( - new SpanProcessorModel() - .setExtensionProperty( - experimentalStacktrace, properties("filter", SPAN_STACKTRACE_FILTER_CLASS))); - } else { - SpanProcessorModel spanProcessorModel = modelOptional.get(); - Object existing = spanProcessorModel.getExtensionProperties().get(experimentalStacktrace); - Map stacktraceProperties = new LinkedHashMap<>(); - - if (existing instanceof Map) { - ((Map) existing).forEach((k, v) -> stacktraceProperties.put(String.valueOf(k), v)); - } - - if (!stacktraceProperties.containsKey("filter")) { - stacktraceProperties.put("filter", SPAN_STACKTRACE_FILTER_CLASS); - spanProcessorModel.setExtensionProperty(experimentalStacktrace, stacktraceProperties); - } - } - - model.setProcessors(allProcessors); - } - private void addSampler(TracerProviderModel model) { SamplerModel sampler = model.getSampler(); if (sampler == null) { diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/StacktraceComponentProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/StacktraceComponentProvider.java new file mode 100644 index 00000000..f455a628 --- /dev/null +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/StacktraceComponentProvider.java @@ -0,0 +1,72 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * Licensed 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 com.solarwinds.opentelemetry.extensions.config.provider; + +import com.google.auto.service.AutoService; +import com.solarwinds.opentelemetry.extensions.SpanStacktraceFilter; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.contrib.stacktrace.StackTraceComponentProvider; +import io.opentelemetry.contrib.stacktrace.StackTraceSpanProcessor; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.trace.SpanProcessor; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Drop-in replacement for {@code stacktrace/development} that defaults the span filter to {@link + * SpanStacktraceFilter}. The span attribute keys that trigger capture are set with {@code + * selectors}; {@code db.system} always applies. + */ +@AutoService(ComponentProvider.class) +public class StacktraceComponentProvider implements ComponentProvider { + + public static final String COMPONENT_NAME = "swo_stacktrace"; + + private static final long DEFAULT_MIN_DURATION_MILLIS = 5; + + @Override + public Class getType() { + return SpanProcessor.class; + } + + @Override + public String getName() { + return COMPONENT_NAME; + } + + @Override + public SpanProcessor create(DeclarativeConfigProperties declarativeConfigProperties) { + long minDurationMillis = + declarativeConfigProperties.getLong("min_duration", DEFAULT_MIN_DURATION_MILLIS); + + if (minDurationMillis < 0) { + return SpanProcessor.composite(); + } + + if (declarativeConfigProperties.getString("filter") != null) { + return new StackTraceComponentProvider().create(declarativeConfigProperties); + } + + List selectors = + declarativeConfigProperties.getScalarList( + "selectors", String.class, Collections.emptyList()); + SpanStacktraceFilter filter = new SpanStacktraceFilter(selectors); + + return new StackTraceSpanProcessor(TimeUnit.MILLISECONDS.toNanos(minDurationMillis), filter); + } +} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java index b51760b6..a7debb21 100644 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java @@ -28,6 +28,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.trace.ReadableSpan; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.junit.jupiter.api.AfterEach; @@ -109,6 +110,18 @@ void returnTrueWhenSpanHasCustomConfiguredLongArrayAttribute() throws InvalidCon assertTrue(filter.test(span)); } + @Test + void explicitFiltersAreUsedWithDbSystemDefault() { + SpanStacktraceFilter filter = new SpanStacktraceFilter(Collections.singletonList("process.id")); + + assertTrue( + filter.test(createSpanWithAttributes(Attributes.builder().put("process.id", 1).build()))); + assertTrue( + filter.test(createSpanWithAttributes(Attributes.builder().put("db.system", "x").build()))); + assertFalse( + filter.test(createSpanWithAttributes(Attributes.builder().put("other", "x").build()))); + } + private ReadableSpan createSpanWithAttributes(Attributes attributes) { ReadableSpan span = mock(ReadableSpan.class); when(span.getAttributes()).thenReturn(attributes); diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/StacktraceFilterParserTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/StacktraceFilterParserTest.java deleted file mode 100644 index c8d0d31a..00000000 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/StacktraceFilterParserTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * Licensed 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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.solarwinds.joboe.config.InvalidConfigException; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class StacktraceFilterParserTest { - private static DeclarativeConfigProperties declarativeConfigProperties; - private final StacktraceFilterParser tested = new StacktraceFilterParser(); - - @BeforeAll - static void setup() { - try (InputStream resourceAsStream = - StacktraceFilterParserTest.class.getResourceAsStream("/sdk-config.yaml")) { - DeclarativeConfigProperties configProperties = - DeclarativeConfiguration.toConfigProperties(resourceAsStream); - declarativeConfigProperties = - configProperties - .getStructured("instrumentation/development", DeclarativeConfigProperties.empty()) - .getStructured("java", DeclarativeConfigProperties.empty()) - .getStructured("solarwinds"); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - void testConvert() throws InvalidConfigException { - Set attributes = tested.convert(declarativeConfigProperties); - assertEquals(new HashSet<>(Collections.singleton("http.request.method")), attributes); - } - - @Test - void configKey() { - assertEquals("agent.spanStacktraceFilters", tested.configKey()); - } -} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java index c5508dd6..3a100dbf 100644 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java @@ -50,7 +50,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -108,7 +107,7 @@ void testCustomize() { SamplerModel sampler = tracerProvider.getSampler(); assertNotNull(sampler.getExtensionProperties().get(SamplerComponentProvider.COMPONENT_NAME)); - assertEquals(2, tracerProvider.getProcessors().size()); + assertEquals(1, tracerProvider.getProcessors().size()); PeriodicMetricReaderModel periodic = openTelemetryConfigurationModel.getMeterProvider().getReaders().get(0).getPeriodic(); @@ -171,7 +170,7 @@ void testCustomize1() { SamplerModel sampler = tracerProvider.getSampler(); assertNotNull(sampler.getExtensionProperties().get(SamplerComponentProvider.COMPONENT_NAME)); - assertEquals(2, tracerProvider.getProcessors().size()); + assertEquals(1, tracerProvider.getProcessors().size()); PeriodicMetricReaderModel periodic = openTelemetryConfigurationModel.getMeterProvider().getReaders().get(0).getPeriodic(); @@ -319,141 +318,6 @@ void customizeShouldNotSetTraceExporterWhenSimpleIsSpecified() { assertNull(spanExporterPropertyModel); } - @Test - void testCustomizeSetsExperimentalStacktraceWhenNotSet() { - OpenTelemetryConfigurationModel openTelemetryConfigurationModel = - withInstrumentation( - new OpenTelemetryConfigurationModel() - .setTracerProvider( - new TracerProviderModel().setProcessors(Collections.emptyList())), - new ExperimentalInstrumentationModel() - .setJava( - new ExperimentalLanguageSpecificInstrumentationModel() - .setAdditionalProperty( - "solarwinds", - new ExperimentalLanguageSpecificInstrumentationPropertyModel()))); - - doNothing() - .when(declarativeConfigurationCustomizerMock) - .addModelCustomizer(functionArgumentCaptor.capture()); - - tested.customize(declarativeConfigurationCustomizerMock); - functionArgumentCaptor.getValue().apply(openTelemetryConfigurationModel); - TracerProviderModel tracerProvider = openTelemetryConfigurationModel.getTracerProvider(); - - assertNotNull(tracerProvider); - assertTrue( - tracerProvider.getProcessors().stream() - .anyMatch( - processorModel -> - processorModel.getExtensionProperties().containsKey("stacktrace/development"))); - } - - @Test - @SuppressWarnings("unchecked") - void testCustomizeSetExperimentalStacktraceFilterWhenNotSet() { - OpenTelemetryConfigurationModel openTelemetryConfigurationModel = - withInstrumentation( - new OpenTelemetryConfigurationModel() - .setTracerProvider( - new TracerProviderModel() - .setProcessors( - Collections.singletonList( - new SpanProcessorModel() - .setExtensionProperty( - "stacktrace/development", new HashMap())))), - new ExperimentalInstrumentationModel() - .setJava( - new ExperimentalLanguageSpecificInstrumentationModel() - .setAdditionalProperty( - "solarwinds", - new ExperimentalLanguageSpecificInstrumentationPropertyModel()))); - - doNothing() - .when(declarativeConfigurationCustomizerMock) - .addModelCustomizer(functionArgumentCaptor.capture()); - - tested.customize(declarativeConfigurationCustomizerMock); - functionArgumentCaptor.getValue().apply(openTelemetryConfigurationModel); - TracerProviderModel tracerProvider = openTelemetryConfigurationModel.getTracerProvider(); - - assertNotNull(tracerProvider); - Optional> map = - tracerProvider.getProcessors().stream() - .filter( - processorModel -> - processorModel.getExtensionProperties().containsKey("stacktrace/development")) - .map( - processorModel -> - asMap(processorModel.getExtensionProperties().get("stacktrace/development"))) - .findFirst(); - - assertTrue(map.isPresent()); - assertNotNull(map.get().get("filter")); - } - - @Test - void testCustomizeSetsStacktraceFilterWhenStacktraceValueIsNotAMap() { - Map stacktrace = customizeWithStacktrace(null); - - assertNotNull(stacktrace.get("filter")); - } - - @Test - void testCustomizeKeepsUserStacktraceFilterAndOtherProperties() { - Map userProperties = new HashMap<>(); - userProperties.put("filter", "com.example.UserFilter"); - userProperties.put("min_duration", 5); - - Map stacktrace = customizeWithStacktrace(userProperties); - - assertEquals("com.example.UserFilter", stacktrace.get("filter")); - assertEquals(5, stacktrace.get("min_duration")); - } - - @Test - void testCustomizeAddsStacktraceFilterAndKeepsOtherProperties() { - Map userProperties = new HashMap<>(); - userProperties.put("min_duration", 5); - - Map stacktrace = customizeWithStacktrace(userProperties); - - assertNotNull(stacktrace.get("filter")); - assertEquals(5, stacktrace.get("min_duration")); - } - - private Map customizeWithStacktrace(Object stacktraceValue) { - OpenTelemetryConfigurationModel model = - withInstrumentation( - new OpenTelemetryConfigurationModel() - .setTracerProvider( - new TracerProviderModel() - .setProcessors( - Collections.singletonList( - new SpanProcessorModel() - .setExtensionProperty( - "stacktrace/development", stacktraceValue)))), - new ExperimentalInstrumentationModel() - .setJava( - new ExperimentalLanguageSpecificInstrumentationModel() - .setAdditionalProperty( - "solarwinds", - new ExperimentalLanguageSpecificInstrumentationPropertyModel()))); - - doNothing() - .when(declarativeConfigurationCustomizerMock) - .addModelCustomizer(functionArgumentCaptor.capture()); - - tested.customize(declarativeConfigurationCustomizerMock); - functionArgumentCaptor.getValue().apply(model); - - return model.getTracerProvider().getProcessors().stream() - .filter(p -> p.getExtensionProperties().containsKey("stacktrace/development")) - .map(p -> asMap(p.getExtensionProperties().get("stacktrace/development"))) - .findFirst() - .orElseThrow(AssertionError::new); - } - @Test void testCustomize2() { OpenTelemetryConfigurationModel openTelemetryConfigurationModel = diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/StacktraceComponentProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/StacktraceComponentProviderTest.java new file mode 100644 index 00000000..c3ff9ff0 --- /dev/null +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/StacktraceComponentProviderTest.java @@ -0,0 +1,166 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * Licensed 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 com.solarwinds.opentelemetry.extensions.config.provider; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.solarwinds.joboe.config.ConfigManager; +import com.solarwinds.joboe.config.ConfigProperty; +import com.solarwinds.joboe.config.InvalidConfigException; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; +import io.opentelemetry.semconv.CodeAttributes; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class StacktraceComponentProviderTest { + + private final StacktraceComponentProvider tested = new StacktraceComponentProvider(); + + @AfterEach + void tearDown() { + ConfigManager.reset(); + } + + @Test + void testName() { + assertEquals("swo_stacktrace", tested.getName()); + } + + @Test + void negativeMinDurationDisablesStacktraceCapture() { + DeclarativeConfigProperties config = config(-1, null); + ReadWriteSpan span = span(Long.MAX_VALUE, "db.system"); + + SpanProcessor processor = tested.create(config); + assertTrue(!processor.isEndRequired() && !processor.isStartRequired()); + end(processor, span); + + verify(span, never()).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + @Test + void capturesStacktraceForDefaultFilterAttribute() { + ReadWriteSpan span = span(TimeUnit.MILLISECONDS.toNanos(10), "db.system"); + + end(tested.create(config(5, null)), span); + verify(span).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + @Test + void capturesStacktraceForAttributeInSelectorsProperty() { + DeclarativeConfigProperties config = config(5, null); + when(config.getScalarList(eq("selectors"), eq(String.class), anyList())) + .thenReturn(Collections.singletonList("http.request.method")); + ReadWriteSpan span = span(TimeUnit.MILLISECONDS.toNanos(10), "http.request.method"); + + end(tested.create(config), span); + verify(span).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + @Test + void ignoresLegacyAgentFilterConfig() throws InvalidConfigException { + ConfigManager.setConfig( + ConfigProperty.AGENT_SPAN_STACKTRACE_FILTERS, Collections.singleton("process.pid")); + ReadWriteSpan span = span(TimeUnit.MILLISECONDS.toNanos(10), "process.pid"); + + end(tested.create(config(5, null)), span); + verify(span, never()).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + @Test + void skipsSpansWithoutFilterAttribute() { + ReadWriteSpan span = span(TimeUnit.MILLISECONDS.toNanos(10), "unrelated.attribute"); + + end(tested.create(config(5, null)), span); + verify(span, never()).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + @Test + void skipsSpansShorterThanMinDuration() { + ReadWriteSpan span = span(TimeUnit.MILLISECONDS.toNanos(1), "db.system"); + + end(tested.create(config(5, null)), span); + verify(span, never()).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + @Test + void zeroMinDurationCapturesEveryMatchingSpan() { + ReadWriteSpan span = span(0, "db.system"); + + end(tested.create(config(0, null)), span); + verify(span).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + @Test + void usesUserSuppliedFilterInsteadOfDefault() { + ReadWriteSpan span = span(TimeUnit.MILLISECONDS.toNanos(10), "unrelated.attribute"); + + end(tested.create(config(5, AcceptAllFilter.class.getName())), span); + verify(span).setAttribute(eq(CodeAttributes.CODE_STACKTRACE), anyString()); + } + + public static class AcceptAllFilter implements Predicate { + @Override + public boolean test(ReadableSpan span) { + return true; + } + } + + private static void end(SpanProcessor processor, ReadWriteSpan span) { + if (processor instanceof ExtendedSpanProcessor) { + ((ExtendedSpanProcessor) processor).onEnding(span); + } + } + + private static DeclarativeConfigProperties config(long minDuration, String filter) { + DeclarativeConfigProperties config = mock(DeclarativeConfigProperties.class); + when(config.getLong(eq("min_duration"), anyLong())).thenReturn(minDuration); + + when(config.getString("filter")).thenReturn(filter); + when(config.getScalarList(eq("selectors"), eq(String.class), anyList())) + .thenAnswer(i -> i.getArgument(2)); + return config; + } + + private static ReadWriteSpan span(long latencyNanos, String attributeKey) { + ReadWriteSpan span = mock(ReadWriteSpan.class); + when(span.getLatencyNanos()).thenReturn(latencyNanos); + + when(span.getAttributes()).thenReturn(Attributes.of(stringKey(attributeKey), "v")); + when(span.getInstrumentationScopeInfo()).thenReturn(InstrumentationScopeInfo.create("test")); + return span; + } +} diff --git a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml b/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml index 6639c184..9b4c9361 100644 --- a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml +++ b/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml @@ -32,8 +32,6 @@ instrumentation/development: agent.logging: level: trace agent.tracingMode: enabled - agent.spanStacktraceFilters: - - http.request.method agent.transactionSettings: - tracing: "disabled" diff --git a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml b/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml index 1d14403f..4ff7b446 100644 --- a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml +++ b/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml @@ -32,8 +32,6 @@ instrumentation/development: agent.logging: level: trace agent.tracingMode: enabled - agent.spanStacktraceFilters: - - http.request.method agent.transactionSettings: - regex: ".*/specialties" diff --git a/libs/shared/src/test/resources/sdk-config.yaml b/libs/shared/src/test/resources/sdk-config.yaml index 1dc9b960..47e56c13 100644 --- a/libs/shared/src/test/resources/sdk-config.yaml +++ b/libs/shared/src/test/resources/sdk-config.yaml @@ -147,8 +147,6 @@ instrumentation/development: agent.logging: level: trace agent.tracingMode: enabled - agent.spanStacktraceFilters: - - http.request.method agent.transactionSettings: - regex: ".*/specialties" diff --git a/sdk-config.yaml b/sdk-config.yaml index b065d4c3..57a3f2d8 100644 --- a/sdk-config.yaml +++ b/sdk-config.yaml @@ -48,6 +48,7 @@ propagator: # Configure tracer provider. tracer_provider: processors: + - swo_stacktrace: - swo_response_time: - swo_profiler: enabled: false diff --git a/smoke-tests/sdk-config.yaml b/smoke-tests/sdk-config.yaml index 311f886b..85ff14e3 100644 --- a/smoke-tests/sdk-config.yaml +++ b/smoke-tests/sdk-config.yaml @@ -72,8 +72,11 @@ tracer_provider: - com.sun - sun - sunw - - stacktrace/development: + - swo_stacktrace: min_duration: 0 + selectors: + - http.request.method + - process.pid - # Configure a batch span processor. simple: # Configure exporter. @@ -179,6 +182,3 @@ instrumentation/development: tracing: "disabled" agent.serviceKey: ${SW_APM_SERVICE_KEY} agent.collector: apm.collector.na-01.st-ssp.solarwinds.com - agent.spanStacktraceFilters: - - http.request.method - - process.pid \ No newline at end of file