From ea4a93339170f58bb2c582944c3c3cf82ded5b8f Mon Sep 17 00:00:00 2001 From: cleverchuk Date: Thu, 21 Nov 2024 15:33:15 -0500 Subject: [PATCH 1/2] NH-94685: capture stacktrace for span --- build.gradle | 2 +- .../extensions/LambdaConfigurationLoader.java | 1 + .../extensions/PropertiesSupplier.java | 3 + custom/shared/build.gradle | 3 + .../opentelemetry/extensions/SharedNames.java | 3 + .../extensions/SpanStacktraceFilter.java | 44 +++++++ .../extensions/StackTraceFilterParser.java | 45 +++++++ .../extensions/SpanStacktraceFilterTest.java | 47 +++++++ .../StackTraceFilterParserTest.java | 53 ++++++++ .../SolarwindsPropertiesSupplier.java | 3 + .../initialize/ConfigurationLoader.java | 2 + gradle.properties | 3 +- .../instrumentation/BackTraceCache.java | 38 ------ .../instrumentation/BackTraceUtil.java | 103 --------------- .../JdbcConnectionInstrumentation.java | 2 +- .../JdbcStatementInstrumentation.java | 2 +- .../instrumentation/StatementTracer.java | 4 +- smoke-tests/k6/basic.js | 124 +++++++++++------- .../test/java/com/solarwinds/LambdaTest.java | 9 ++ .../test/java/com/solarwinds/SmokeTest.java | 9 ++ .../java/com/solarwinds/agents/Agent.java | 6 +- 21 files changed, 306 insertions(+), 200 deletions(-) create mode 100644 custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java create mode 100644 custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParser.java create mode 100644 custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java create mode 100644 custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParserTest.java delete mode 100644 instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/BackTraceCache.java delete mode 100644 instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/BackTraceUtil.java diff --git a/build.gradle b/build.gradle index a8a25103..723440ab 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ subprojects { opentelemetryJavaagent: property('otel.agent.version'), bytebuddy : "1.15.10", guava : "30.1-jre", - joboe : "10.0.15", + joboe : "10.0.16", agent : swoVersion, // the custom distro agent version autoservice : "1.0.1", caffeine : "2.9.3", diff --git a/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java b/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java index b7f74bc6..b2e5e29b 100644 --- a/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java +++ b/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java @@ -64,6 +64,7 @@ public class LambdaConfigurationLoader { ConfigProperty.AGENT_TRIGGER_TRACE_ENABLED.setParser(ModeStringToBooleanParser.INSTANCE); ConfigProperty.AGENT_TRANSACTION_NAMING_SCHEMES.setParser(new TransactionNamingSchemesParser()); ConfigProperty.AGENT_SQL_TAG_DATABASES.setParser(new SqlTagDatabasesParser()); + ConfigProperty.AGENT_SPAN_STACK_TRACE_FILTERS.setParser(new StackTraceFilterParser()); } public static void load() throws InvalidConfigException { diff --git a/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/PropertiesSupplier.java b/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/PropertiesSupplier.java index 704a259c..3c2bbdda 100644 --- a/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/PropertiesSupplier.java +++ b/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/PropertiesSupplier.java @@ -17,6 +17,7 @@ package com.solarwinds.opentelemetry.extensions; import static com.solarwinds.opentelemetry.extensions.SharedNames.COMPONENT_NAME; +import static com.solarwinds.opentelemetry.extensions.SharedNames.SPAN_STACKTRACE_FILTER_CLASS; import java.util.HashMap; import java.util.Map; @@ -30,6 +31,8 @@ public PropertiesSupplier() { defaultProperties.put( "otel.propagators", String.format("tracecontext,baggage,%s,xray", COMPONENT_NAME)); defaultProperties.put("otel.instrumentation.runtime-telemetry.enabled", "false"); + defaultProperties.put( + "otel.java.experimental.span-stacktrace.filter", SPAN_STACKTRACE_FILTER_CLASS); defaultProperties.put("otel.exporter.otlp.protocol", "grpc"); } diff --git a/custom/shared/build.gradle b/custom/shared/build.gradle index b3877c98..87b94958 100644 --- a/custom/shared/build.gradle +++ b/custom/shared/build.gradle @@ -40,6 +40,9 @@ dependencies { implementation "org.json:json:${versions.json}" implementation "com.google.code.gson:gson:2.10.1" implementation "com.github.ben-manes.caffeine:caffeine:${versions.caffeine}" + implementation("io.opentelemetry.contrib:opentelemetry-span-stacktrace:${property('otel.java.contrib.version')}") { + exclude(module: "opentelemetry-sdk", group: "io.opentelemetry") // the agent includes this + } testImplementation("org.json:json:${versions.json}") testImplementation "com.solarwinds.joboe:sampling:${versions.joboe}" diff --git a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java index 5f7f99ed..2bcb469c 100644 --- a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java +++ b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java @@ -23,6 +23,9 @@ private SharedNames() {} public static String TRANSACTION_NAME_KEY = "sw.transaction"; + public static String SPAN_STACKTRACE_FILTER_CLASS = + "com.solarwinds.opentelemetry.extensions.SpanStacktraceFilter"; + // This is visible to customer via span layer and can be used to configure transaction // filtering setting. public static final String LAYER_NAME_PLACEHOLDER = "%s:%s"; diff --git a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java new file mode 100644 index 00000000..f701cf6c --- /dev/null +++ b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java @@ -0,0 +1,44 @@ +/* + * © 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; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import com.solarwinds.joboe.config.ConfigManager; +import com.solarwinds.joboe.config.ConfigProperty; +import io.opentelemetry.sdk.trace.ReadableSpan; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +public class SpanStacktraceFilter implements Predicate { + private static final Set filterAttributes = new HashSet<>(); + + static { + filterAttributes.add("db.system"); + Set configuredFilterAttributes = + ConfigManager.getConfigOptional( + ConfigProperty.AGENT_SPAN_STACK_TRACE_FILTERS, filterAttributes); + filterAttributes.addAll(configuredFilterAttributes); + } + + @Override + public boolean test(ReadableSpan readableSpan) { + return filterAttributes.stream() + .anyMatch(attr -> readableSpan.getAttribute(stringKey(attr)) != null); + } +} diff --git a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParser.java b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParser.java new file mode 100644 index 00000000..3e5e59cf --- /dev/null +++ b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParser.java @@ -0,0 +1,45 @@ +/* + * © 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; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.solarwinds.joboe.config.ConfigParser; +import com.solarwinds.joboe.config.InvalidConfigException; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public final class StackTraceFilterParser implements ConfigParser> { + private static final Gson gson = new GsonBuilder().create(); + + @Override + public Set convert(String input) throws InvalidConfigException { + try { + if (input.startsWith("[")) { + Type type = new TypeToken>() {}.getType(); + return gson.fromJson(input, type); + } + return Arrays.stream(input.split(",")).map(String::trim).collect(Collectors.toSet()); + } catch (JsonSyntaxException e) { + throw new InvalidConfigException(e); + } + } +} diff --git a/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java b/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java new file mode 100644 index 00000000..80628c95 --- /dev/null +++ b/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilterTest.java @@ -0,0 +1,47 @@ +/* + * © 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; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import io.opentelemetry.sdk.trace.ReadableSpan; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SpanStacktraceFilterTest { + + @InjectMocks private SpanStacktraceFilter tested; + + @Mock private ReadableSpan readableSpanMock; + + @Test + void returnTrueWhenAttributeHasValue() { + when(readableSpanMock.getAttribute(any())).thenReturn("test-value"); + assertTrue(tested.test(readableSpanMock)); + } + + @Test + void returnFalseWhenAttributeHasNoValue() { + assertFalse(tested.test(readableSpanMock)); + } +} diff --git a/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParserTest.java b/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParserTest.java new file mode 100644 index 00000000..fb906471 --- /dev/null +++ b/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParserTest.java @@ -0,0 +1,53 @@ +/* + * © 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; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.solarwinds.joboe.config.InvalidConfigException; +import java.util.HashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class StackTraceFilterParserTest { + + @InjectMocks private StackTraceFilterParser tested; + + @Test + void returnListOfStringGivenCommaSeparatedString() throws InvalidConfigException { + String input = "name, status"; + Set expected = new HashSet<>(); + expected.add("name"); + + expected.add("status"); + assertEquals(expected, tested.convert(input)); + } + + @Test + void returnListOfStringGivenJsonList() throws InvalidConfigException { + String input = "[\"name\",\"status\"]"; + Set expected = new HashSet<>(); + expected.add("name"); + + expected.add("status"); + assertEquals(expected, tested.convert(input)); + } +} diff --git a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsPropertiesSupplier.java b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsPropertiesSupplier.java index a4765474..b09636bb 100644 --- a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsPropertiesSupplier.java +++ b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsPropertiesSupplier.java @@ -17,6 +17,7 @@ package com.solarwinds.opentelemetry.extensions; import static com.solarwinds.opentelemetry.extensions.SharedNames.COMPONENT_NAME; +import static com.solarwinds.opentelemetry.extensions.SharedNames.SPAN_STACKTRACE_FILTER_CLASS; import static com.solarwinds.opentelemetry.extensions.initialize.AutoConfigurationCustomizerProviderImpl.isAgentEnabled; import java.util.HashMap; @@ -31,6 +32,8 @@ public class SolarwindsPropertiesSupplier implements Supplier, String> backTraceCache = - Caffeine.newBuilder() - .maximumSize(20) - .expireAfterAccess(Duration.ofHours(1L)) - .build(); // 1 hour cache; - - static String getBackTraceString(List stackTrace) { - return backTraceCache.getIfPresent(stackTrace); - } - - static void putBackTraceString(List stackTrace, String stackTraceString) { - backTraceCache.put(stackTrace, stackTraceString); - } -} diff --git a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/BackTraceUtil.java b/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/BackTraceUtil.java deleted file mode 100644 index 8a8d5b51..00000000 --- a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/BackTraceUtil.java +++ /dev/null @@ -1,103 +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.instrumentation; - -import com.solarwinds.joboe.logging.Logger; -import com.solarwinds.joboe.logging.LoggerFactory; -import java.util.Arrays; -import java.util.List; - -public class BackTraceUtil { - private static final int MAX_BACK_TRACE_TOP_LINE_COUNT = 100; - - private static final int MAX_BACK_TRACE_BOTTOM_LINE_COUNT = 20; - - private static final int MAX_BACK_TRACE_LINE_COUNT = - MAX_BACK_TRACE_TOP_LINE_COUNT + MAX_BACK_TRACE_BOTTOM_LINE_COUNT; - - private static final Logger logger = LoggerFactory.getLogger(); - - public static StackTraceElement[] getBackTrace(int skipElements) { - StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - - int startPosition = - 2 + skipElements; // Starts with 2: To exclude the getStackTrace() and addBackTrace calls - // themselves. Also adds the number of skipElements provided in the - // argument to skip elements - - if (startPosition >= stackTrace.length) { - logger.debug( - "Attempt to skip [" - + skipElements - + "] elements in addBackTrace is invalid, no stack trace element is left!"); - return new StackTraceElement[0]; - } - - int targetStackTraceLength = stackTrace.length - startPosition; - StackTraceElement[] targetStackTrace = new StackTraceElement[targetStackTraceLength]; - System.arraycopy(stackTrace, startPosition, targetStackTrace, 0, targetStackTraceLength); - - return targetStackTrace; - } - - public static String backTraceToString(StackTraceElement[] stackTrace) { - List wrappedStackTrace = - Arrays.asList(stackTrace); // wrap it so hashCode and equals work - - String cachedValue = BackTraceCache.getBackTraceString(wrappedStackTrace); - if (cachedValue != null) { - return cachedValue; - } - - StringBuffer stringBuffer = new StringBuffer(); - if (stackTrace.length > MAX_BACK_TRACE_LINE_COUNT) { // then we will have to skip some lines - appendStackTrace( - stackTrace, 0, MAX_BACK_TRACE_TOP_LINE_COUNT, stringBuffer); // add the top lines - - stringBuffer - .append("...Skipped ") - .append(stackTrace.length - MAX_BACK_TRACE_LINE_COUNT) - .append(" line(s)\n"); - - appendStackTrace( - stackTrace, - stackTrace.length - MAX_BACK_TRACE_BOTTOM_LINE_COUNT, - MAX_BACK_TRACE_BOTTOM_LINE_COUNT, - stringBuffer); // add the bottom lines - - } else { - appendStackTrace(stackTrace, 0, stackTrace.length, stringBuffer); // add everything - } - - String value = stringBuffer.toString(); - BackTraceCache.putBackTraceString(wrappedStackTrace, value); - return value; - } - - /** - * Build the stackTrace output and append the result to the buffer provided - * - * @param stackTrace The source of the stack trace array - * @param buffer The buffer that stores the result - */ - private static void appendStackTrace( - StackTraceElement[] stackTrace, int startPosition, int lineCount, StringBuffer buffer) { - for (int i = startPosition; i < startPosition + lineCount && i < stackTrace.length; i++) { - buffer.append(stackTrace[i].toString()).append("\n"); - } - } -} diff --git a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcConnectionInstrumentation.java b/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcConnectionInstrumentation.java index fa17fa99..d8984454 100644 --- a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcConnectionInstrumentation.java +++ b/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcConnectionInstrumentation.java @@ -88,7 +88,7 @@ public static void start( } sql = TraceContextInjector.inject(currentContext(), sql); - StatementTracer.writeStackTraceSpec(currentContext()); + StatementTracer.writeQuerySpec(currentContext()); } @Advice.OnMethodExit(suppress = Throwable.class) diff --git a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcStatementInstrumentation.java b/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcStatementInstrumentation.java index c6e1ae5f..2cdcc7a0 100644 --- a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcStatementInstrumentation.java +++ b/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/JdbcStatementInstrumentation.java @@ -90,7 +90,7 @@ public static void onEnter( } sql = TraceContextInjector.inject(currentContext(), sql); - StatementTracer.writeStackTraceSpec(currentContext()); + StatementTracer.writeQuerySpec(currentContext()); StatementTruncator.maybeTruncateStatement(currentContext()); } diff --git a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/StatementTracer.java b/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/StatementTracer.java index 78704e12..f3ab6b90 100644 --- a/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/StatementTracer.java +++ b/instrumentation/jdbc/src/main/java/com/solarwinds/opentelemetry/instrumentation/StatementTracer.java @@ -21,11 +21,9 @@ import io.opentelemetry.context.Context; public class StatementTracer { - public static void writeStackTraceSpec(Context context) { + public static void writeQuerySpec(Context context) { Span span = Span.fromContext(context); if (span.getSpanContext().isSampled()) { - String backTraceString = BackTraceUtil.backTraceToString(BackTraceUtil.getBackTrace(1)); - span.setAttribute(Constants.SW_KEY_PREFIX + "Backtrace", backTraceString); span.setAttribute(Constants.SW_KEY_PREFIX + "Spec", "query"); } } diff --git a/smoke-tests/k6/basic.js b/smoke-tests/k6/basic.js index 4d45becd..3bb2ffdf 100644 --- a/smoke-tests/k6/basic.js +++ b/smoke-tests/k6/basic.js @@ -381,67 +381,83 @@ function verify_that_metrics_are_reported(metric, checkFn, service="lambda-e2e") } } -function verify_transaction_name() { - let retryCount = Number.parseInt(`${__ENV.SWO_RETRY_COUNT}`) || 1000; - for (; retryCount > 0; retryCount--) { - const newOwner = names.randomOwner(); - const response = http.post(`${baseUri}/owners`, JSON.stringify(newOwner), - { - headers: { - 'Content-Type': 'application/json', - } - } - ); +function check_transaction_name(property) { + if (property.key === "sw.transaction") { + check(property, {"transaction-name": prop => prop.value === "lambda-test-txn"}) + return true; + } + return false +} - const traceContext = response.headers['X-Trace'] - const [_, traceId, __, flag] = traceContext.split("-") - console.log("Trace context -> ", traceContext) - if (flag === '00') continue; - const spanRawDataPayload = { - "operationName": "getSubSpanRawData", - "variables": { - "traceId": traceId.toUpperCase() - }, - "query": "query getSubSpanRawData($traceId: ID!, $spanFilter: TraceArchiveSpanFilter, $incomplete: Boolean) {\n traceArchive(\n traceId: $traceId\n spanFilter: $spanFilter\n incomplete: $incomplete\n ) {\n traceId\n traceSpans {\n edges {\n node {\n events {\n eventId\n properties {\n key\n value\n __typename\n }\n __typename\n }\n spanId\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n" +function check_code_stack_trace(property) { + if (property.key === "code.stacktrace") { + check(property, {"code.stacktrace": _ => true}) + return true; } + return false +} +function check_property(fn) { + let retryCount = Number.parseInt(`${__ENV.SWO_RETRY_COUNT}`) || 1000; for (; retryCount > 0; retryCount--) { - let spanDataResponse = http.post(`${__ENV.SWO_HOST_URL}/common/graphql`, JSON.stringify(spanRawDataPayload), - { - headers: { - 'Content-Type': 'application/json', - 'Cookie': `${__ENV.SWO_COOKIE}`, - 'X-Csrf-Token': `${__ENV.SWO_XSR_TOKEN}` + const newOwner = names.randomOwner(); + const response = http.post(`${baseUri}/owners`, JSON.stringify(newOwner), + { + headers: { + 'Content-Type': 'application/json', + } } - }); + ); - spanDataResponse = JSON.parse(spanDataResponse.body) - if (spanDataResponse['errors']) { - console.log("Error -> Transaction name response:", JSON.stringify(spanDataResponse)) - continue - } + const traceContext = response.headers['X-Trace'] + const [_, traceId, __, flag] = traceContext.split("-") + console.log("Trace context -> ", traceContext) + if (flag === '00') continue; - const {data: {traceArchive: {traceSpans: {edges}}}} = spanDataResponse - for (let i = 0; i < edges.length; i++) { - const edge = edges[i] - const {node: {events}} = edge + const spanRawDataPayload = { + "operationName": "getSubSpanRawData", + "variables": { + "traceId": traceId.toUpperCase() + }, + "query": "query getSubSpanRawData($traceId: ID!, $spanFilter: TraceArchiveSpanFilter, $incomplete: Boolean) {\n traceArchive(\n traceId: $traceId\n spanFilter: $spanFilter\n incomplete: $incomplete\n ) {\n traceId\n traceSpans {\n edges {\n node {\n events {\n eventId\n properties {\n key\n value\n __typename\n }\n __typename\n }\n spanId\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n" + } - for (let j = 0; j < events.length; j++) { - const event = events[j] - const {properties} = event + for (; retryCount > 0; retryCount--) { + let spanDataResponse = http.post(`${__ENV.SWO_HOST_URL}/common/graphql`, JSON.stringify(spanRawDataPayload), + { + headers: { + 'Content-Type': 'application/json', + 'Cookie': `${__ENV.SWO_COOKIE}`, + 'X-Csrf-Token': `${__ENV.SWO_XSR_TOKEN}` + } + }); - for (let k = 0; k < properties.length; k++) { - const property = properties[k] - if (property.key === "sw.transaction") { - check(property, {"transaction-name": prop => prop.value === "lambda-test-txn"}) - return; + spanDataResponse = JSON.parse(spanDataResponse.body) + if (spanDataResponse['errors']) { + console.log("Error -> Transaction name response:", JSON.stringify(spanDataResponse)) + continue + } + + const {data: {traceArchive: {traceSpans: {edges}}}} = spanDataResponse + for (let i = 0; i < edges.length; i++) { + const edge = edges[i] + const {node: {events}} = edge + + for (let j = 0; j < events.length; j++) { + const event = events[j] + const {properties} = event + + for (let k = 0; k < properties.length; k++) { + const property = properties[k] + if (fn(property)) { + return; + } + } + } } - } } - } } - } } function getEntityId() { @@ -577,7 +593,13 @@ export default function () { verify_that_metrics_are_reported("trace.service.response_time", response_time) }) - silence(verify_transaction_name) + silence(function () { + check_property(check_transaction_name) + }) + + silence(function () { + check_property(check_code_stack_trace) + }) } else { const service = "java-apm-smoke-test" @@ -606,7 +628,9 @@ export default function () { silence(verify_logs_export) silence(verify_that_specialty_path_is_not_sampled) - + silence(function () { + check_property(check_code_stack_trace) + }) silence(verify_that_span_data_is_persisted) silence(verify_that_trace_is_persisted) silence(verify_distributed_trace) diff --git a/smoke-tests/src/test/java/com/solarwinds/LambdaTest.java b/smoke-tests/src/test/java/com/solarwinds/LambdaTest.java index acdfd2b0..4b92d486 100644 --- a/smoke-tests/src/test/java/com/solarwinds/LambdaTest.java +++ b/smoke-tests/src/test/java/com/solarwinds/LambdaTest.java @@ -154,4 +154,13 @@ void assertSDKTransactionNaming() throws IOException { double passes = ResultsCollector.read(resultJson, "$.root_group.checks.['custom transaction name'].passes"); assertTrue(passes > 1, "SDK transaction naming is broken"); } + + @Test + void assertThatCodeStacktraceIsCaptured() throws IOException { + String resultJson = new String( + Files.readAllBytes(namingConventions.local.k6Results(Configs.E2E.config.agents().get(0)))); + + double passes = ResultsCollector.read(resultJson, "$.root_group.checks.['code.stacktrace'].passes"); + assertTrue(passes > 0, "Expects a count > 0"); + } } diff --git a/smoke-tests/src/test/java/com/solarwinds/SmokeTest.java b/smoke-tests/src/test/java/com/solarwinds/SmokeTest.java index 778ce36f..b1b6b574 100644 --- a/smoke-tests/src/test/java/com/solarwinds/SmokeTest.java +++ b/smoke-tests/src/test/java/com/solarwinds/SmokeTest.java @@ -295,4 +295,13 @@ void assertThatResponseTimeMetricIsReported() throws IOException { double passes = ResultsCollector.read(resultJson, "$.root_group.checks.['response_time'].passes"); assertTrue(passes > 0, "Expects a count > 0"); } + + @Test + void assertThatCodeStacktraceIsCaptured() throws IOException { + String resultJson = new String( + Files.readAllBytes(namingConventions.local.k6Results(Configs.E2E.config.agents().get(0)))); + + double passes = ResultsCollector.read(resultJson, "$.root_group.checks.['code.stacktrace'].passes"); + assertTrue(passes > 0, "Expects a count > 0"); + } } diff --git a/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java b/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java index 425a5533..79abfba9 100644 --- a/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java +++ b/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java @@ -22,7 +22,7 @@ import java.net.URI; import java.net.URL; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; @Value @@ -40,7 +40,9 @@ public Agent(String name, String description) { } public Agent(String name, String description, String url) { - this(name, description, url, Collections.singletonList("-Dio.opentelemetry.context.enableStrictContext=true")); + this(name, description, url, Arrays.asList("-Dio.opentelemetry.context.enableStrictContext=true", + "-Dotel.java.experimental.span-stacktrace.min.duration=0ms", + "-Dsw.apm.span.stack.trace.filters=thread.id,os.description,http.request.method")); } public Agent(String name, String description, String url, List additionalJvmArgs) { From 02e9da6550d80e3b2019d280b8372163fde87c47 Mon Sep 17 00:00:00 2001 From: cleverchuk Date: Tue, 24 Dec 2024 13:15:35 -0500 Subject: [PATCH 2/2] NH-94685: name change for span stacktrace stuff --- build.gradle | 2 +- .../opentelemetry/extensions/LambdaConfigurationLoader.java | 2 +- .../opentelemetry/extensions/SpanStacktraceFilter.java | 2 +- ...tackTraceFilterParser.java => StacktraceFilterParser.java} | 2 +- ...eFilterParserTest.java => StacktraceFilterParserTest.java} | 4 ++-- .../extensions/initialize/ConfigurationLoader.java | 4 ++-- smoke-tests/src/test/java/com/solarwinds/agents/Agent.java | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/{StackTraceFilterParser.java => StacktraceFilterParser.java} (96%) rename custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/{StackTraceFilterParserTest.java => StacktraceFilterParserTest.java} (94%) diff --git a/build.gradle b/build.gradle index 723440ab..8c132905 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ subprojects { opentelemetryJavaagent: property('otel.agent.version'), bytebuddy : "1.15.10", guava : "30.1-jre", - joboe : "10.0.16", + joboe : "10.0.17", agent : swoVersion, // the custom distro agent version autoservice : "1.0.1", caffeine : "2.9.3", diff --git a/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java b/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java index b2e5e29b..104a94ef 100644 --- a/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java +++ b/custom/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaConfigurationLoader.java @@ -64,7 +64,7 @@ public class LambdaConfigurationLoader { ConfigProperty.AGENT_TRIGGER_TRACE_ENABLED.setParser(ModeStringToBooleanParser.INSTANCE); ConfigProperty.AGENT_TRANSACTION_NAMING_SCHEMES.setParser(new TransactionNamingSchemesParser()); ConfigProperty.AGENT_SQL_TAG_DATABASES.setParser(new SqlTagDatabasesParser()); - ConfigProperty.AGENT_SPAN_STACK_TRACE_FILTERS.setParser(new StackTraceFilterParser()); + ConfigProperty.AGENT_SPAN_STACKTRACE_FILTERS.setParser(new StacktraceFilterParser()); } public static void load() throws InvalidConfigException { diff --git a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java index f701cf6c..98b0c480 100644 --- a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java +++ b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SpanStacktraceFilter.java @@ -32,7 +32,7 @@ public class SpanStacktraceFilter implements Predicate { filterAttributes.add("db.system"); Set configuredFilterAttributes = ConfigManager.getConfigOptional( - ConfigProperty.AGENT_SPAN_STACK_TRACE_FILTERS, filterAttributes); + ConfigProperty.AGENT_SPAN_STACKTRACE_FILTERS, filterAttributes); filterAttributes.addAll(configuredFilterAttributes); } diff --git a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParser.java b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StacktraceFilterParser.java similarity index 96% rename from custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParser.java rename to custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StacktraceFilterParser.java index 3e5e59cf..e0ef4738 100644 --- a/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParser.java +++ b/custom/shared/src/main/java/com/solarwinds/opentelemetry/extensions/StacktraceFilterParser.java @@ -27,7 +27,7 @@ import java.util.Set; import java.util.stream.Collectors; -public final class StackTraceFilterParser implements ConfigParser> { +public final class StacktraceFilterParser implements ConfigParser> { private static final Gson gson = new GsonBuilder().create(); @Override diff --git a/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParserTest.java b/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StacktraceFilterParserTest.java similarity index 94% rename from custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParserTest.java rename to custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StacktraceFilterParserTest.java index fb906471..21b3c036 100644 --- a/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StackTraceFilterParserTest.java +++ b/custom/shared/src/test/java/com/solarwinds/opentelemetry/extensions/StacktraceFilterParserTest.java @@ -27,9 +27,9 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class StackTraceFilterParserTest { +class StacktraceFilterParserTest { - @InjectMocks private StackTraceFilterParser tested; + @InjectMocks private StacktraceFilterParser tested; @Test void returnListOfStringGivenCommaSeparatedString() throws InvalidConfigException { diff --git a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/initialize/ConfigurationLoader.java b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/initialize/ConfigurationLoader.java index fcd1e8ab..a2bbfbb6 100644 --- a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/initialize/ConfigurationLoader.java +++ b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/initialize/ConfigurationLoader.java @@ -42,7 +42,7 @@ import com.solarwinds.opentelemetry.extensions.NamingScheme; import com.solarwinds.opentelemetry.extensions.RangeValidationParser; import com.solarwinds.opentelemetry.extensions.SqlTagDatabasesParser; -import com.solarwinds.opentelemetry.extensions.StackTraceFilterParser; +import com.solarwinds.opentelemetry.extensions.StacktraceFilterParser; import com.solarwinds.opentelemetry.extensions.TracingModeParser; import com.solarwinds.opentelemetry.extensions.TransactionNameManager; import com.solarwinds.opentelemetry.extensions.TransactionNamingScheme; @@ -102,7 +102,7 @@ public class ConfigurationLoader { ConfigProperty.PROFILER.setParser(ProfilerSettingParser.INSTANCE); ConfigProperty.AGENT_TRANSACTION_NAMING_SCHEMES.setParser(new TransactionNamingSchemesParser()); ConfigProperty.AGENT_SQL_TAG_DATABASES.setParser(new SqlTagDatabasesParser()); - ConfigProperty.AGENT_SPAN_STACK_TRACE_FILTERS.setParser(new StackTraceFilterParser()); + ConfigProperty.AGENT_SPAN_STACKTRACE_FILTERS.setParser(new StacktraceFilterParser()); } public static void load() throws InvalidConfigException { diff --git a/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java b/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java index 79abfba9..02e849e1 100644 --- a/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java +++ b/smoke-tests/src/test/java/com/solarwinds/agents/Agent.java @@ -42,7 +42,7 @@ public Agent(String name, String description) { public Agent(String name, String description, String url) { this(name, description, url, Arrays.asList("-Dio.opentelemetry.context.enableStrictContext=true", "-Dotel.java.experimental.span-stacktrace.min.duration=0ms", - "-Dsw.apm.span.stack.trace.filters=thread.id,os.description,http.request.method")); + "-Dsw.apm.span.stacktrace.filters=thread.id,os.description,http.request.method")); } public Agent(String name, String description, String url, List additionalJvmArgs) {