Replace FilteredStringWriter with direct StackTraceElement usage.
MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
@@ -7,10 +7,8 @@ import datadog.trace.api.sampling.PrioritySampling;
import datadog.trace.common.util.Clock;
import io.opentracing.Span;
import io.opentracing.tag.Tag;
-import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.io.Writer;
import java.lang.ref.WeakReference;
import java.math.BigInteger;
import java.util.HashMap;
@@ -107,101 +105,28 @@ public class DDSpan implements Span, MutableSpan {
private void addStacktraceIfThresholdExceeded() {
final long spanDurationStacktraceNanos = Config.get().getSpanDurationStacktraceNanos();
- if (!isError()
- && spanDurationStacktraceNanos > 0
- && durationNano.get() > spanDurationStacktraceNanos
+ if (isError()
+ || spanDurationStacktraceNanos <= 0
+ || durationNano.get() <= spanDurationStacktraceNanos
// If this span was finished async, then the stacktrace will be less meaningful.
- && context.threadId == Thread.currentThread().getId()) {
- final Exception stacktrace = new Exception();
- final Writer stackString = new FilteredStringWriter();
- stacktrace.printStackTrace(new PrintWriter(stackString));
- setTag("slow.stack", stackString.toString());
- }
- }
-
- // Writer that skips the first line and lines until FILTER doesn't match.
- private static final class FilteredStringWriter extends Writer {
- private static final char[] FILTER = "\tat datadog.opentracing.".toCharArray();
-
- private final StringWriter writer = new StringWriter();
-
- private State state = State.SKIP_LINE; // Skip the exception type and message
- private StringBuilder buffer = new StringBuilder();
- private int lineIndex = 0;
-
- @Override
- public void write(final char[] cbuf, final int off, final int len) throws IOException {
- if ((off < 0)
- || (off > cbuf.length)
- || (len < 0)
- || ((off + len) > cbuf.length)
- || ((off + len) < 0)) {
- throw new IndexOutOfBoundsException();
- } else if (len == 0) {
+ || context.threadId != Thread.currentThread().getId()) {
return;
}
-
- for (int i = off; i < len; i++) {
- switch (state) {
- case SKIP_LINE:
- // Should work fine for windows "\r\n" case too.
- if (cbuf[i] == '\n') {
- state = State.READ_LINE;
- lineIndex = 0;
- }
- break;
-
- case READ_LINE:
- if (lineIndex == FILTER.length) {
- state = State.SKIP_LINE;
- buffer = new StringBuilder();
- } else if (cbuf[i] == FILTER[lineIndex]) {
- buffer.append(cbuf[i]);
- lineIndex++;
- } else {
- // writer.write(buffer.toString());
- // writer.write(cbuf[i]);
- // buffer = new StringBuilder();
- // state = State.CONTINUE;
- state = State.SKIP_NEXT_LINE_THEN_CONTINUE;
- }
+ final StringBuilder stack = new StringBuilder();
+ // TODO: use StackWalker api for java 9+ jvm's?
+ boolean skipNext = true;
+ for (final StackTraceElement element : Thread.currentThread().getStackTrace()) {
+ final boolean tracingClass = element.getClassName().startsWith("datadog.opentracing.");
+ if (skipNext || tracingClass) {
+ skipNext = tracingClass;
continue;
-
- case SKIP_NEXT_LINE_THEN_CONTINUE:
- // Should work fine for windows "\r\n" case too.
- if (cbuf[i] == '\n') {
- state = State.CONTINUE;
}
- break;
-
- case CONTINUE:
- writer.write(cbuf, i, len - (i - off));
- return;
- }
- }
- }
-
- @Override
- public void flush() {
- writer.flush();
- }
-
- @Override
- public void close() throws IOException {
- writer.close();
- }
-
- @Override
- public String toString() {
- return buffer.toString() + writer.toString();
- }
-
- private enum State {
- READ_LINE,
- SKIP_LINE,
- SKIP_NEXT_LINE_THEN_CONTINUE,
- CONTINUE
+ stack.append("\tat ");
+ stack.append(element);
+ stack.append("\n");
}
+ // Prob not worth dealing with the trailing newline.
+ setTag("slow.stack", stack.toString());
}
@Override

MODIFY
dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy
dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanTest.groovy
package datadog.opentracing
-
import datadog.opentracing.propagation.ExtractedContext
import datadog.opentracing.propagation.TagContext
import datadog.trace.api.Config
@@ -9,6 +8,7 @@ import datadog.trace.common.sampling.RateByServiceSampler
import datadog.trace.common.writer.ListWriter
import datadog.trace.util.test.DDSpecification
import io.opentracing.SpanContext
+import org.spockframework.util.ReflectionUtil
import java.util.concurrent.TimeUnit
@@ -172,17 +172,23 @@ class DDSpanTest extends DDSpecification {
def "stacktrace captured when duration exceeds configured threshold"() {
setup:
- def stackString = new StringWriter()
- new Exception().printStackTrace(new PrintWriter(stackString))
// Get the part of the stack before this test is called.
- def stack = stackString.toString().split(getClass().getName())[1].split('\n', 2)[1]
+ def acceptRemaining = false
+ def originalStack = Thread.currentThread().stackTrace
+ def stackTraceElements = originalStack.dropWhile {
+ if (it.className == ReflectionUtil.name) {
+ acceptRemaining = true
+ }
+ return !acceptRemaining
+ }
+ def stack = "\tat " + stackTraceElements.join("\n\tat ") + "\n"
def span = tracer.buildSpan("test").start()
span.finish(span.startTimeMicro + TimeUnit.NANOSECONDS.toMicros(Config.get().spanDurationStacktraceNanos) + 1)
- def actual = span.tags["slow.stack"]
+ def actual = span.tags["slow.stack"].toString()
expect:
- !actual.toString().isEmpty()
+ !stack.isEmpty()
actual.endsWith(stack)
}

Replace FilteredStringWriter with direct StackTraceElement usage.