Introduce the LogsHandler
MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
@@ -50,6 +50,9 @@ public class DDSpan implements Span, MutableSpan {
*/
private final AtomicLong durationNano = new AtomicLong();
+ /** Delegates to for handling the logs if present. */
+ private final LogsHandler logsHandler;
+
/** Implementation detail. Stores the weak reference to this span. Used by TraceCollection. */
volatile WeakReference<DDSpan> ref;
@@ -60,7 +63,19 @@ public class DDSpan implements Span, MutableSpan {
* @param context the context used for the span
*/
DDSpan(final long timestampMicro, final DDSpanContext context) {
+ this(timestampMicro, context, null);
+ }
+
+ /**
+ * Spans should be constructed using the builder, not by calling the constructor directly.
+ *
+ * @param timestampMicro if greater than zero, use this time instead of the current time
+ * @param context the context used for the span
+ * @param logsHandler as the handler where to delegate the log actions
+ */
+ DDSpan(final long timestampMicro, final DDSpanContext context, final LogsHandler logsHandler) {
this.context = context;
+ this.logsHandler = logsHandler;
if (timestampMicro <= 0L) {
// record the start time
@@ -227,7 +242,9 @@ public class DDSpan implements Span, MutableSpan {
*/
@Override
public final DDSpan log(final Map<String, ?> map) {
- if (!extractError(map)) {
+ if (!extractError(map) && logsHandler != null) {
+ logsHandler.log(map);
+ } else {
log.debug("`log` method is not implemented. Doing nothing");
}
return this;
@@ -238,7 +255,9 @@ public class DDSpan implements Span, MutableSpan {
*/
@Override
public final DDSpan log(final long l, final Map<String, ?> map) {
- if (!extractError(map)) {
+ if (!extractError(map) && logsHandler != null) {
+ logsHandler.log(l, map);
+ } else {
log.debug("`log` method is not implemented. Doing nothing");
}
return this;
@@ -249,7 +268,11 @@ public class DDSpan implements Span, MutableSpan {
*/
@Override
public final DDSpan log(final String s) {
+ if (logsHandler != null) {
+ logsHandler.log(s);
+ } else {
log.debug("`log` method is not implemented. Provided log: {}", s);
+ }
return this;
}
@@ -258,7 +281,11 @@ public class DDSpan implements Span, MutableSpan {
*/
@Override
public final DDSpan log(final long l, final String s) {
+ if (logsHandler != null) {
+ logsHandler.log(l, s);
+ } else {
log.debug("`log` method is not implemented. Provided log: {}", s);
+ }
return this;
}

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
@@ -580,6 +580,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
private boolean errorFlag;
private String spanType;
private boolean ignoreScope = false;
+ private LogsHandler logsHandler = null;
public DDSpanBuilder(final String operationName, final ScopeManager scopeManager) {
this.operationName = operationName;
@@ -593,7 +594,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
}
private Span startSpan() {
- return new DDSpan(timestampMicro, buildSpanContext());
+ return new DDSpan(timestampMicro, buildSpanContext(), logsHandler);
}
@Override
@@ -670,6 +671,11 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
return parent.baggageItems();
}
+ public DDSpanBuilder withLogsHandler(LogsHandler logsHandler) {
+ this.logsHandler = logsHandler;
+ return this;
+ }
+
@Override
public DDSpanBuilder asChildOf(final Span span) {
return asChildOf(span == null ? null : span.context());

ADD
dd-trace-ot/src/main/java/datadog/opentracing/LogsHandler.java
+package datadog.opentracing;
+
+import java.util.Map;
+
+public interface LogsHandler {
+
+ /**
+ * Handles the log implementation in the Span.
+ *
+ * @param fields key:value log fields. Tracer implementations should support String, numeric, and
+ * boolean values; some may also support arbitrary Objects.
+ */
+ void log(Map<String, ?> fields);
+
+ /**
+ * Handles the log implementation in the Span.
+ *
+ * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or
+ * equal to the Span's start timestamp.
+ * @param fields key:value log fields. Tracer implementations should support String, numeric, and
+ * boolean values; some may also support arbitrary Objects.
+ */
+ void log(long timestampMicroseconds, Map<String, ?> fields);
+
+ /**
+ * Handles the log implementation in the Span..
+ *
+ * @param event the event value; often a stable identifier for a moment in the Span lifecycle
+ */
+ void log(String event);
+
+ /**
+ * Handles the log implementation in the Span.
+ *
+ * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or
+ * equal to the Span's start timestamp.
+ * @param event the event value; often a stable identifier for a moment in the Span lifecycle
+ */
+ void log(long timestampMicroseconds, String event);
+}

MODIFY
dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy
dd-trace-ot/src/test/groovy/datadog/opentracing/DDSpanBuilderTest.groovy
@@ -8,7 +8,6 @@ import datadog.trace.common.writer.ListWriter
import datadog.trace.util.test.DDSpecification
import io.opentracing.Scope
import io.opentracing.noop.NoopSpan
-
import static datadog.opentracing.DDSpanContext.ORIGIN_KEY
import static java.util.concurrent.TimeUnit.MILLISECONDS
@@ -480,4 +479,143 @@ class DDSpanBuilderTest extends DDSpecification {
"a:a,a:b,a:c" | [a: "c"]
"a:1,b-c:d" | [a: "1", "b-c": "d"]
}
+
+ def "sanity test for logs if logsHandler is null"() {
+ setup:
+ final String expectedName = "fakeName"
+
+ final DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withServiceName("foo")
+ .start()
+ final String expectedLogEvent = "fakeEvent"
+ final timeStamp = System.currentTimeMillis()
+ final Map<String, String> fieldsMap = new HashMap<>()
+
+ span.log(expectedLogEvent)
+ span.log(timeStamp, expectedLogEvent)
+ span.log(fieldsMap)
+ span.log(timeStamp, fieldsMap)
+ }
+
+
+ def "should delegate simple logs to logHandler"() {
+ setup:
+ final LogsHandler logsHandler = new TestLogsHandler()
+ final String expectedName = "fakeName"
+
+ final DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withLogsHandler(logsHandler)
+ .withServiceName("foo")
+ .start()
+ final String expectedLogEvent = "fakeEvent"
+ final timeStamp = System.currentTimeMillis()
+ span.log(timeStamp, expectedLogEvent)
+
+ expect:
+ logsHandler.assertLogCalledWithArgs(timeStamp, expectedLogEvent)
+ }
+
+ def "should delegate simple logs with timestamp to logHandler"() {
+ setup:
+ final LogsHandler logsHandler = new TestLogsHandler()
+ final String expectedName = "fakeName"
+
+ final DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withLogsHandler(logsHandler)
+ .withServiceName("foo")
+ .start()
+ final String expectedLogEvent = "fakeEvent"
+ span.log(expectedLogEvent)
+
+ expect:
+ logsHandler.assertLogCalledWithArgs(expectedLogEvent)
+
+ }
+
+ def "should delegate logs with fields to logHandler"() {
+ setup:
+ final LogsHandler logsHandler = new TestLogsHandler()
+ final String expectedName = "fakeName"
+
+ final DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withLogsHandler(logsHandler)
+ .withServiceName("foo")
+ .start()
+ final Map<String, String> fieldsMap = new HashMap<>()
+ span.log(fieldsMap)
+
+ expect:
+ logsHandler.assertLogCalledWithArgs(fieldsMap)
+
+ }
+
+ def "should delegate logs with fields and timestamp to logHandler"() {
+ setup:
+ final LogsHandler logsHandler = new TestLogsHandler()
+ final String expectedName = "fakeName"
+
+ final DDSpan span =
+ tracer
+ .buildSpan(expectedName)
+ .withLogsHandler(logsHandler)
+ .withServiceName("foo")
+ .start()
+ final Map<String, String> fieldsMap = new HashMap<>()
+ final timeStamp = System.currentTimeMillis()
+ span.log(timeStamp, fieldsMap)
+
+ expect:
+ logsHandler.assertLogCalledWithArgs(timeStamp, fieldsMap)
+
+ }
+
+ private static class TestLogsHandler implements LogsHandler {
+ Object[] arguments = null
+
+ @Override
+ void log(Map<String, ?> fields) {
+ arguments = new Object[1]
+ arguments[0] = fields
+ }
+
+ @Override
+ void log(long timestampMicroseconds, Map<String, ?> fields) {
+ arguments = new Object[2]
+ arguments[0] = timestampMicroseconds
+ arguments[1] = fields
+ }
+
+ @Override
+ void log(String event) {
+ arguments = new Object[1]
+ arguments[0] = event
+ }
+
+ @Override
+ void log(long timestampMicroseconds, String event) {
+ arguments = new Object[2]
+ arguments[0] = timestampMicroseconds
+ arguments[1] = event
+ }
+
+ boolean assertLogCalledWithArgs(Object... args) {
+ if (arguments.size() != args.size()) {
+ return false
+ }
+ for (int i = 0; i < args.size(); i++) {
+ if (arguments[i] != args[i]) {
+ return false
+ }
+ }
+ return true
+ }
+ }
}

Introduce the LogsHandler