diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java index fb2dc67c99..df125345b4 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java @@ -327,6 +327,14 @@ public String toJsonString(final ErrorCapture error) { return s; } + public String toJsonString(final StackTraceElement stackTraceElement) { + jw.reset(); + serializeStackTraceElement(stackTraceElement); + final String s = jw.toString(); + jw.reset(); + return s; + } + public String toString() { return jw.toString(); } @@ -656,10 +664,20 @@ private void serializeStackTraceElement(StackTraceElement stacktrace) { writeField("function", stacktrace.getMethodName()); writeField("library_frame", isLibraryFrame(stacktrace.getClassName())); writeField("lineno", stacktrace.getLineNumber()); - writeLastField("abs_path", stacktrace.getClassName()); + serializeStackFrameModule(stacktrace.getClassName()); jw.writeByte(OBJECT_END); } + private void serializeStackFrameModule(final String fullyQualifiedClassName) { + writeFieldName("module"); + replaceBuilder.setLength(0); + final int lastDotIndex = fullyQualifiedClassName.lastIndexOf('.'); + if (lastDotIndex > 0) { + replaceBuilder.append(fullyQualifiedClassName, 0, lastDotIndex); + } + writeStringBuilderValue(replaceBuilder, jw); + } + private boolean isLibraryFrame(String className) { for (String applicationPackage : stacktraceConfiguration.getApplicationPackages()) { if (className.startsWith(applicationPackage)) { diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java index 433909b02c..8c06c8af06 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java @@ -52,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class DslJsonSerializerTest { @@ -61,7 +62,9 @@ class DslJsonSerializerTest { @BeforeEach void setUp() { - serializer = new DslJsonSerializer(mock(StacktraceConfiguration.class)); + StacktraceConfiguration stacktraceConfiguration = mock(StacktraceConfiguration.class); + when(stacktraceConfiguration.getStackTraceLimit()).thenReturn(15); + serializer = new DslJsonSerializer(stacktraceConfiguration); objectMapper = new ObjectMapper(); } @@ -97,7 +100,15 @@ void testErrorSerialization() throws IOException { assertThat(context.get("tags").get("foo").textValue()).isEqualTo("bar"); JsonNode exception = errorTree.get("exception"); assertThat(exception.get("message").textValue()).isEqualTo("test"); - assertThat(exception.get("stacktrace")).isNotNull(); + JsonNode stacktrace = exception.get("stacktrace"); + assertThat(stacktrace).isNotNull(); + assertThat(stacktrace).hasSize(15); + JsonNode stackTraceElement = stacktrace.get(0); + assertThat(stackTraceElement.get("filename")).isNotNull(); + assertThat(stackTraceElement.get("function")).isNotNull(); + assertThat(stackTraceElement.get("library_frame")).isNotNull(); + assertThat(stackTraceElement.get("lineno")).isNotNull(); + assertThat(stackTraceElement.get("module")).isNotNull(); assertThat(exception.get("type").textValue()).isEqualTo(Exception.class.getName()); assertThat(errorTree.get("transaction").get("sampled").booleanValue()).isTrue(); assertThat(errorTree.get("transaction").get("type").textValue()).isEqualTo("test-type"); diff --git a/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java b/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java index d4f3d93b48..2642ad3e43 100644 --- a/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java +++ b/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java @@ -73,9 +73,9 @@ void setUp() throws IOException { void fillStackTrace() { assertThat(stacktrace).isNotEmpty(); // even though the stacktrace is captured within our tracer class, the first method should be getStackTrace - assertThat(stacktrace.get(0).get("abs_path").textValue()).doesNotStartWith("co.elastic"); + assertThat(stacktrace.get(0).get("module").textValue()).doesNotStartWith("co.elastic"); assertThat(stacktrace.get(0).get("function").textValue()).isEqualTo("getStackTrace"); - assertThat(stacktrace.stream().filter(st -> st.get("abs_path").textValue().endsWith("StacktraceSerializationTest"))).isNotEmpty(); + assertThat(stacktrace.stream().filter(st -> st.get("filename").textValue().equals("StacktraceSerializationTest.java"))).isNotEmpty(); } @Test @@ -83,7 +83,7 @@ void testAppFrame() throws Exception { when(stacktraceConfiguration.getApplicationPackages()).thenReturn(Collections.singletonList("org.example.stacktrace")); stacktrace = getStackTrace(); Optional thisMethodsFrame = stacktrace.stream() - .filter(st -> st.get("abs_path").textValue().startsWith(getClass().getName())) + .filter(st -> st.get("module").textValue().equals(getClass().getPackageName())) .findAny(); assertThat(thisMethodsFrame).isPresent(); assertThat(thisMethodsFrame.get().get("library_frame").booleanValue()).isFalse(); @@ -92,7 +92,7 @@ void testAppFrame() throws Exception { @Test void testNoAppFrame() { Optional thisMethodsFrame = stacktrace.stream() - .filter(st -> st.get("abs_path").textValue().startsWith(getClass().getName())) + .filter(st -> st.get("module").textValue().startsWith(getClass().getPackageName())) .findAny(); assertThat(thisMethodsFrame).isPresent(); assertThat(thisMethodsFrame.get().get("library_frame").booleanValue()).isTrue(); @@ -106,11 +106,46 @@ void testFileNamePresent() { @Test void testNoInternalStackFrames() { assertSoftly(softly -> { - softly.assertThat(stacktrace.stream().filter(st -> st.get("abs_path").textValue().contains("java.lang.reflect."))).isEmpty(); - softly.assertThat(stacktrace.stream().filter(st -> st.get("abs_path").textValue().contains("sun."))).isEmpty(); + softly.assertThat(stacktrace.stream().filter(st -> st.get("module").textValue().startsWith("java.lang."))).isEmpty(); + softly.assertThat(stacktrace.stream().filter(st -> st.get("module").textValue().startsWith("sun."))).isEmpty(); }); } + @Test + void testStackTraceElementSerialization() throws IOException { + when(stacktraceConfiguration.getApplicationPackages()).thenReturn(Collections.singletonList("co.elastic.apm")); + + StackTraceElement stackTraceElement = new StackTraceElement("co.elastic.apm.test.TestClass", + "testMethod", "TestClass.java", 34); + String json = serializer.toJsonString(stackTraceElement); + JsonNode stackTraceElementParsed = objectMapper.readTree(json); + assertThat(stackTraceElementParsed.get("filename").textValue()).isEqualTo("TestClass.java"); + assertThat(stackTraceElementParsed.get("function").textValue()).isEqualTo("testMethod"); + assertThat(stackTraceElementParsed.get("library_frame").booleanValue()).isFalse(); + assertThat(stackTraceElementParsed.get("lineno").intValue()).isEqualTo(34); + assertThat(stackTraceElementParsed.get("module").textValue()).isEqualTo("co.elastic.apm.test"); + + stackTraceElement = new StackTraceElement("co.elastic.TestClass", + "testMethod", "TestClass.java", 34); + json = serializer.toJsonString(stackTraceElement); + stackTraceElementParsed = objectMapper.readTree(json); + assertThat(stackTraceElementParsed.get("library_frame").booleanValue()).isTrue(); + assertThat(stackTraceElementParsed.get("module").textValue()).isEqualTo("co.elastic"); + + stackTraceElement = new StackTraceElement(".TestClass", + "testMethod", "TestClass.java", 34); + json = serializer.toJsonString(stackTraceElement); + stackTraceElementParsed = objectMapper.readTree(json); + assertThat(stackTraceElementParsed.get("module").textValue()).isEqualTo(""); + + stackTraceElement = new StackTraceElement("TestClass", + "testMethod", "TestClass.java", 34); + json = serializer.toJsonString(stackTraceElement); + stackTraceElementParsed = objectMapper.readTree(json); + assertThat(stackTraceElementParsed.get("module").textValue()).isEqualTo(""); + } + + private List getStackTrace() throws IOException { final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); final Span span = transaction.createSpan();