diff --git a/.github/workflows/rlscan.yml b/.github/workflows/rlscan.yml index 0972c7ac..8f50de80 100644 --- a/.github/workflows/rlscan.yml +++ b/.github/workflows/rlscan.yml @@ -8,6 +8,10 @@ on: permissions: contents: read +env: + GITHUB_USERNAME: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + jobs: reversing_lab_scan_agent: runs-on: ubuntu-latest 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/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) {