From 03b566ea5dff02f9bad1b61b5ddc4518e2604aad Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Mon, 7 Jan 2019 18:15:14 +0200 Subject: [PATCH] Setting transaction.sampled in errors (#405) --- .../apm/agent/impl/ElasticApmTracer.java | 1 + .../apm/agent/impl/error/ErrorCapture.java | 13 +++++++++ .../report/serialize/DslJsonSerializer.java | 14 ++++++++- .../apm/agent/impl/ElasticApmTracerTest.java | 29 +++++++++++++++---- .../serialize/DslJsonSerializerTest.java | 24 +++++++++++++++ 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index 4129e130bf..014ecabcf4 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java @@ -248,6 +248,7 @@ else if (active instanceof Span) { error.getContext().getTags().putAll(span.getContext().getTags()); } error.asChildOf(active); + error.setTransactionSampled(active.isSampled()); } else { error.getTraceContext().getId().setToRandomValue(); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/error/ErrorCapture.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/error/ErrorCapture.java index 8261b54768..8416e27408 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/error/ErrorCapture.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/error/ErrorCapture.java @@ -55,6 +55,10 @@ public class ErrorCapture implements Recyclable { * (Required) */ private long timestamp; + /** + * A hint for UI to be able to show whether a recorded trace for the corresponding transaction is expected + */ + private boolean isTransactionSampled; private ElasticApmTracer tracer; private final StringBuilder culprit = new StringBuilder(); @@ -97,6 +101,7 @@ public void resetState() { exception = null; context.resetState(); timestamp = 0; + isTransactionSampled = false; traceContext.resetState(); culprit.setLength(0); } @@ -171,4 +176,12 @@ private void setCulprit(StackTraceElement stackTraceElement) { } culprit.append(')'); } + + public boolean isTransactionSampled() { + return isTransactionSampled; + } + + public void setTransactionSampled(boolean transactionSampled) { + isTransactionSampled = transactionSampled; + } } 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 c092ea2863..bafc85a58f 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 @@ -226,7 +226,7 @@ private void serializeError(ErrorCapture errorCapture) { jw.writeByte(JsonWriter.OBJECT_START); writeTimestamp(errorCapture.getTimestamp()); - + serializeTransactionSampled(errorCapture.isTransactionSampled()); if (errorCapture.getTraceContext().hasContent()) { serializeTraceContext(errorCapture.getTraceContext(), true); } @@ -237,6 +237,14 @@ private void serializeError(ErrorCapture errorCapture) { jw.writeByte(JsonWriter.OBJECT_END); } + private void serializeTransactionSampled(boolean sampled) { + writeFieldName("transaction"); + jw.writeByte(JsonWriter.OBJECT_START); + writeLastField("sampled", sampled); + jw.writeByte(JsonWriter.OBJECT_END); + jw.writeByte(COMMA); + } + private void serializeException(@Nullable Throwable exception) { writeFieldName("exception"); jw.writeByte(JsonWriter.OBJECT_START); @@ -284,6 +292,10 @@ public String toJsonString(final ErrorCapture error) { return s; } + public String toString() { + return jw.toString(); + } + private void serializeTransactionPayload(final TransactionPayload payload) { jw.writeByte(JsonWriter.OBJECT_START); serializeService(payload.getService()); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java index f9347d3f0c..56f2b1d886 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java @@ -26,6 +26,7 @@ import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.sampling.ConstantSampler; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; +import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; @@ -163,26 +164,44 @@ void testRecordException() { ErrorCapture error = reporter.getFirstError(); assertThat(error.getException()).isNotNull(); assertThat(error.getTraceContext().hasContent()).isFalse(); + assertThat(error.isTransactionSampled()).isFalse(); } @Test void testRecordExceptionWithTrace() { - Transaction transaction = tracerImpl.startTransaction(); + innerRecordExceptionWithTrace(true); + innerRecordExceptionWithTrace(false); + } + + private void innerRecordExceptionWithTrace(boolean sampled) { + reporter.reset(); + Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(sampled), -1); try (Scope scope = transaction.activateInScope()) { transaction.getContext().getRequest() .addHeader("foo", "bar") .withMethod("GET") .getUrl() .withPathname("/foo"); - tracerImpl.currentTransaction().captureException(new Exception("test")); - assertThat(reporter.getErrors()).hasSize(1); - ErrorCapture error = reporter.getFirstError(); - assertThat(error.getTraceContext().isChildOf(transaction.getTraceContext())).isTrue(); + tracerImpl.currentTransaction().captureException(new Exception("from transaction")); + ErrorCapture error = validateError(transaction, sampled); assertThat(error.getContext().getRequest().getHeaders().get("foo")).isEqualTo("bar"); + reporter.reset(); + Span span = transaction.createSpan().activate(); + span.captureException(new Exception("from span")); + validateError(span, sampled); + span.deactivate().end(); transaction.end(); } } + private ErrorCapture validateError(AbstractSpan span, boolean sampled) { + assertThat(reporter.getErrors()).hasSize(1); + ErrorCapture error = reporter.getFirstError(); + assertThat(error.getTraceContext().isChildOf(span.getTraceContext())).isTrue(); + assertThat(error.isTransactionSampled()).isEqualTo(sampled); + return error; + } + @Test void testEnableDropSpans() { when(tracerImpl.getConfig(CoreConfiguration.class).getTransactionMaxSpans()).thenReturn(1); 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 a6417e84bb..02ae73d7f2 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 @@ -19,7 +19,9 @@ */ package co.elastic.apm.agent.report.serialize; +import co.elastic.apm.agent.MockTracer; import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; import co.elastic.apm.agent.impl.transaction.Transaction; import com.dslplatform.json.JsonWriter; @@ -64,6 +66,28 @@ void serializeTags() { }); } + @Test + void testErrorSerialization() throws IOException { + ElasticApmTracer tracer = MockTracer.create(); + Transaction transaction = new Transaction(tracer); + ErrorCapture error = new ErrorCapture(tracer).asChildOf(transaction).withTimestamp(5000); + error.setTransactionSampled(true); + error.setException(new Exception("test")); + error.getContext().getTags().put("foo", "bar"); + String errorJson = serializer.toJsonString(error); + System.out.println("errorJson = " + errorJson); + JsonNode errorTree = objectMapper.readTree(errorJson); + assertThat(errorTree.get("timestamp").longValue()).isEqualTo(5000); + assertThat(errorTree.get("culprit").textValue()).startsWith(this.getClass().getName()); + JsonNode context = errorTree.get("context"); + 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(); + assertThat(exception.get("type").textValue()).isEqualTo(Exception.class.getName()); + assertThat(errorTree.get("transaction").get("sampled").booleanValue()).isTrue(); + } + @Test void testLimitStringValueLength() throws IOException { StringBuilder longValue = new StringBuilder(DslJsonSerializer.MAX_VALUE_LENGTH + 1);