Initial support for JSON rendering in new API
This commit is contained in:
parent
20b134e356
commit
ed29969cb8
|
@ -1,8 +1,18 @@
|
|||
package datadog.trace.tracer;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import datadog.trace.api.DDTags;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -10,6 +20,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
/** Concrete implementation of a span */
|
||||
@Slf4j
|
||||
// Disable autodetection of fields and accessors
|
||||
@JsonAutoDetect(
|
||||
fieldVisibility = Visibility.NONE,
|
||||
setterVisibility = Visibility.NONE,
|
||||
getterVisibility = Visibility.NONE,
|
||||
isGetterVisibility = Visibility.NONE,
|
||||
creatorVisibility = Visibility.NONE)
|
||||
class SpanImpl implements Span {
|
||||
|
||||
private final TraceInternal trace;
|
||||
|
@ -68,12 +85,32 @@ class SpanImpl implements Span {
|
|||
return context;
|
||||
}
|
||||
|
||||
@JsonGetter("trace_id")
|
||||
@JsonSerialize(using = UInt64IDStringSerializer.class)
|
||||
public String getTraceId() {
|
||||
return context.getTraceId();
|
||||
}
|
||||
|
||||
@JsonGetter("span_id")
|
||||
@JsonSerialize(using = UInt64IDStringSerializer.class)
|
||||
public String getSpanId() {
|
||||
return context.getSpanId();
|
||||
}
|
||||
|
||||
@JsonGetter("parent_id")
|
||||
@JsonSerialize(using = UInt64IDStringSerializer.class)
|
||||
public String getParentId() {
|
||||
return context.getParentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonGetter("start")
|
||||
public Timestamp getStartTimestamp() {
|
||||
return startTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonGetter("duration")
|
||||
public Long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
@ -84,6 +121,7 @@ class SpanImpl implements Span {
|
|||
}
|
||||
|
||||
@Override
|
||||
@JsonGetter("service")
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
@ -98,6 +136,7 @@ class SpanImpl implements Span {
|
|||
}
|
||||
|
||||
@Override
|
||||
@JsonGetter("resource")
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
@ -112,6 +151,7 @@ class SpanImpl implements Span {
|
|||
}
|
||||
|
||||
@Override
|
||||
@JsonGetter("type")
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
@ -126,6 +166,7 @@ class SpanImpl implements Span {
|
|||
}
|
||||
|
||||
@Override
|
||||
@JsonGetter("name")
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -140,6 +181,8 @@ class SpanImpl implements Span {
|
|||
}
|
||||
|
||||
@Override
|
||||
@JsonGetter("error")
|
||||
@JsonFormat(shape = JsonFormat.Shape.NUMBER)
|
||||
public boolean isErrored() {
|
||||
return errored;
|
||||
}
|
||||
|
@ -169,6 +212,15 @@ class SpanImpl implements Span {
|
|||
}
|
||||
}
|
||||
|
||||
@JsonGetter("meta")
|
||||
synchronized Map<String, String> getMeta() {
|
||||
final Map<String, String> result = new HashMap<>(meta.size());
|
||||
for (final Map.Entry<String, Object> entry : meta.entrySet()) {
|
||||
result.put(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Object getMeta(final String key) {
|
||||
return meta.get(key);
|
||||
|
@ -201,6 +253,8 @@ class SpanImpl implements Span {
|
|||
setMeta(key, (Object) value);
|
||||
}
|
||||
|
||||
// FIXME: Add metrics support and json rendering for metrics
|
||||
|
||||
@Override
|
||||
public synchronized void finish() {
|
||||
if (isFinished()) {
|
||||
|
@ -263,4 +317,19 @@ class SpanImpl implements Span {
|
|||
private void reportSetterUsageError(final String fieldName) {
|
||||
reportUsageError("Attempted to set '%s' when span is already finished: %s", fieldName, this);
|
||||
}
|
||||
|
||||
/** Helper to serialize string value as 64 bit unsigned integer */
|
||||
private static class UInt64IDStringSerializer extends StdSerializer<String> {
|
||||
|
||||
public UInt64IDStringSerializer() {
|
||||
super(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(
|
||||
final String value, final JsonGenerator jsonGenerator, final SerializerProvider provider)
|
||||
throws IOException {
|
||||
jsonGenerator.writeNumber(new BigInteger(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package datadog.trace.tracer;
|
|||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
|
@ -30,9 +31,15 @@ public class Timestamp {
|
|||
return clock;
|
||||
}
|
||||
|
||||
/** @return time since epoch in nanoseconds */
|
||||
@JsonValue
|
||||
public long getTime() {
|
||||
return clock.getStartTimeNano() + startTicksOffset();
|
||||
}
|
||||
|
||||
/** @return duration in nanoseconds from this time stamp to current time. */
|
||||
public long getDuration() {
|
||||
return getDuration(new Timestamp(clock));
|
||||
return getDuration(clock.createCurrentTimestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,10 +57,7 @@ public class Timestamp {
|
|||
clock, finishTimestamp.clock);
|
||||
// Do our best to try to calculate nano-second time using millisecond clock start time and
|
||||
// nanosecond offset.
|
||||
return max(
|
||||
0,
|
||||
(finishTimestamp.clock.getStartTimeNano() + finishTimestamp.startTicksOffset())
|
||||
- (clock.getStartTimeNano() + startTicksOffset()));
|
||||
return max(0, finishTimestamp.getTime() - getTime());
|
||||
}
|
||||
return max(0, finishTimestamp.nanoTicks - nanoTicks);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package datadog.trace.tracer;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import datadog.trace.tracer.writer.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -58,6 +59,7 @@ class TraceImpl implements TraceInternal {
|
|||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public synchronized List<Span> getSpans() {
|
||||
if (!finished) {
|
||||
tracer.reportError("Cannot get spans, trace is not finished yet: %s", this);
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package datadog.trace.tracer
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
/**
|
||||
* Helper class to parse serialized span to verify serialization logic
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
class JsonSpan {
|
||||
@JsonProperty("trace_id")
|
||||
BigInteger traceId
|
||||
@JsonProperty("parent_id")
|
||||
BigInteger parentId
|
||||
@JsonProperty("span_id")
|
||||
BigInteger spanId
|
||||
|
||||
@JsonProperty("start")
|
||||
long start
|
||||
@JsonProperty("duration")
|
||||
long duration
|
||||
|
||||
@JsonProperty("service")
|
||||
String service
|
||||
@JsonProperty("resource")
|
||||
String resource
|
||||
@JsonProperty("type")
|
||||
String type
|
||||
@JsonProperty("name")
|
||||
String name
|
||||
|
||||
@JsonProperty("error")
|
||||
boolean error
|
||||
|
||||
@JsonProperty("meta")
|
||||
Map<String, String> meta
|
||||
|
||||
@JsonCreator
|
||||
JsonSpan() {}
|
||||
|
||||
JsonSpan(Span span) {
|
||||
traceId = new BigInteger(span.getContext().getTraceId())
|
||||
parentId = new BigInteger(span.getContext().getParentId())
|
||||
spanId = new BigInteger(span.getContext().getSpanId())
|
||||
|
||||
start = span.getStartTimestamp().getTime()
|
||||
duration = span.getDuration()
|
||||
|
||||
service = span.getService()
|
||||
resource = span.getResource()
|
||||
type = span.getType()
|
||||
name = span.getName()
|
||||
|
||||
error = span.isErrored()
|
||||
|
||||
meta = span.getMeta()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package datadog.trace.tracer
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import datadog.trace.api.DDTags
|
||||
import spock.lang.Specification
|
||||
|
||||
|
@ -8,6 +10,7 @@ class SpanImplTest extends Specification {
|
|||
private static final String SERVICE_NAME = "service.name"
|
||||
private static final String PARENT_TRACE_ID = "trace id"
|
||||
private static final String PARENT_SPAN_ID = "span id"
|
||||
private static final long START_TIME = 100
|
||||
private static final long DURATION = 321
|
||||
|
||||
def interceptors = [Mock(name: "interceptor-1", Interceptor), Mock(name: "interceptor-2", Interceptor)]
|
||||
|
@ -20,6 +23,7 @@ class SpanImplTest extends Specification {
|
|||
getSpanId() >> PARENT_SPAN_ID
|
||||
}
|
||||
def startTimestamp = Mock(Timestamp) {
|
||||
getTime() >> START_TIME
|
||||
getDuration() >> DURATION
|
||||
getDuration(_) >> { args -> args[0] + DURATION }
|
||||
}
|
||||
|
@ -27,6 +31,8 @@ class SpanImplTest extends Specification {
|
|||
getTracer() >> tracer
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper()
|
||||
|
||||
def "test setters and default values"() {
|
||||
when: "create span"
|
||||
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
||||
|
@ -38,6 +44,9 @@ class SpanImplTest extends Specification {
|
|||
span.getContext().getTraceId() == PARENT_TRACE_ID
|
||||
span.getContext().getParentId() == PARENT_SPAN_ID
|
||||
span.getContext().getSpanId() ==~ /\d+/
|
||||
span.getTraceId() == PARENT_TRACE_ID
|
||||
span.getParentId() == PARENT_SPAN_ID
|
||||
span.getSpanId() == span.getContext().getSpanId()
|
||||
span.getService() == SERVICE_NAME
|
||||
span.getResource() == null
|
||||
span.getType() == null
|
||||
|
@ -110,6 +119,19 @@ class SpanImplTest extends Specification {
|
|||
"number.key" | 123
|
||||
}
|
||||
|
||||
def "test getMeta"() {
|
||||
setup:
|
||||
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
||||
|
||||
when:
|
||||
span.setMeta("number.key", 123)
|
||||
span.setMeta("string.key", "meta string")
|
||||
span.setMeta("boolean.key", true)
|
||||
|
||||
then:
|
||||
span.getMeta() == ["number.key": "123", "string.key": "meta string", "boolean.key": "true"]
|
||||
}
|
||||
|
||||
def "test meta setter on finished span for #key"() {
|
||||
setup: "create span"
|
||||
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
||||
|
@ -289,4 +311,64 @@ class SpanImplTest extends Specification {
|
|||
then:
|
||||
thrown TraceException
|
||||
}
|
||||
|
||||
def "test JSON rendering"() {
|
||||
setup: "create span"
|
||||
def parentContext = new SpanContextImpl("123", "456", "789")
|
||||
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
||||
span.setResource("test resource")
|
||||
span.setType("test type")
|
||||
span.setName("test name")
|
||||
span.setMeta("number.key", 123)
|
||||
span.setMeta("string.key", "meta string")
|
||||
span.setMeta("boolean.key", true)
|
||||
span.finish()
|
||||
|
||||
when: "convert to JSON"
|
||||
def string = objectMapper.writeValueAsString(span)
|
||||
def parsedSpan = objectMapper.readerFor(JsonSpan).readValue(string)
|
||||
|
||||
then:
|
||||
parsedSpan == new JsonSpan(span)
|
||||
}
|
||||
|
||||
def "test JSON rendering with throwable"() {
|
||||
setup: "create span"
|
||||
def parentContext = new SpanContextImpl("123", "456", "789")
|
||||
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
||||
span.attachThrowable(new RuntimeException("test"))
|
||||
span.finish()
|
||||
|
||||
when: "convert to JSON"
|
||||
def string = objectMapper.writeValueAsString(span)
|
||||
def parsedSpan = objectMapper.readerFor(JsonSpan).readValue(string)
|
||||
|
||||
then:
|
||||
parsedSpan == new JsonSpan(span)
|
||||
}
|
||||
|
||||
def "test JSON rendering with big ID values"() {
|
||||
setup: "create span"
|
||||
def parentContext = new SpanContextImpl(
|
||||
new BigInteger(2).pow(64).subtract(1).toString(),
|
||||
"123",
|
||||
new BigInteger(2).pow(64).subtract(2).toString())
|
||||
def span = new SpanImpl(trace, parentContext, startTimestamp)
|
||||
span.finish()
|
||||
|
||||
when: "convert to JSON"
|
||||
def string = objectMapper.writeValueAsString(span)
|
||||
def parsedSpan = objectMapper.readValue(string, JsonSpan)
|
||||
|
||||
then:
|
||||
parsedSpan == new JsonSpan(span)
|
||||
|
||||
when:
|
||||
def json = objectMapper.readTree(string)
|
||||
|
||||
then: "make sure ids rendered as number"
|
||||
json.get("trace_id").isNumber()
|
||||
json.get("parent_id").isNumber()
|
||||
json.get("span_id").isNumber()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package datadog.trace.tracer
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import nl.jqno.equalsverifier.EqualsVerifier
|
||||
import nl.jqno.equalsverifier.Warning
|
||||
import spock.lang.Specification
|
||||
|
@ -18,6 +19,8 @@ class TimestampTest extends Specification {
|
|||
getTracer() >> tracer
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper()
|
||||
|
||||
def "test getter"() {
|
||||
when:
|
||||
def timestamp = new Timestamp(clock)
|
||||
|
@ -26,6 +29,20 @@ class TimestampTest extends Specification {
|
|||
timestamp.getClock() == clock
|
||||
}
|
||||
|
||||
def "test getTime"() {
|
||||
setup:
|
||||
clock.nanoTicks() >> CLOCK_NANO_TICKS
|
||||
clock.getStartTimeNano() >> CLOCK_START_TIME
|
||||
clock.getStartNanoTicks() >> CLOCK_START_NANO_TICKS
|
||||
def timestamp = new Timestamp(clock)
|
||||
|
||||
when:
|
||||
def time = timestamp.getTime()
|
||||
|
||||
then:
|
||||
time == 300
|
||||
}
|
||||
|
||||
def "test getDuration with literal finish time"() {
|
||||
setup:
|
||||
clock.nanoTicks() >> CLOCK_NANO_TICKS
|
||||
|
@ -62,6 +79,7 @@ class TimestampTest extends Specification {
|
|||
|
||||
def "test getDuration with current time"() {
|
||||
setup:
|
||||
clock.createCurrentTimestamp() >> { new Timestamp(clock) }
|
||||
clock.nanoTicks() >> CLOCK_START_NANO_TICKS >> FINISH_NANO_TICKS
|
||||
def timestamp = new Timestamp(clock)
|
||||
|
||||
|
@ -102,4 +120,19 @@ class TimestampTest extends Specification {
|
|||
then:
|
||||
noExceptionThrown()
|
||||
}
|
||||
|
||||
def "test JSON rendering"() {
|
||||
setup:
|
||||
clock.nanoTicks() >> CLOCK_NANO_TICKS
|
||||
clock.getStartTimeNano() >> CLOCK_START_TIME
|
||||
clock.getStartNanoTicks() >> CLOCK_START_NANO_TICKS
|
||||
def timestamp = new Timestamp(clock)
|
||||
|
||||
when:
|
||||
def string = objectMapper.writeValueAsString(timestamp)
|
||||
|
||||
|
||||
then:
|
||||
string == "300"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package datadog.trace.tracer
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import datadog.trace.tracer.sampling.Sampler
|
||||
import datadog.trace.tracer.writer.Writer
|
||||
import spock.lang.Specification
|
||||
|
||||
|
||||
class TraceImplTest extends Specification {
|
||||
|
||||
private static final String SERVICE_NAME = "service.name"
|
||||
|
@ -35,6 +36,8 @@ class TraceImplTest extends Specification {
|
|||
}
|
||||
def startTimestamp = Mock(Timestamp)
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper()
|
||||
|
||||
def "test getters"() {
|
||||
when:
|
||||
def trace = new TraceImpl(tracer, parentContext, startTimestamp)
|
||||
|
@ -456,4 +459,35 @@ class TraceImplTest extends Specification {
|
|||
then: "error is reported"
|
||||
thrown TraceException
|
||||
}
|
||||
|
||||
def "test JSON rendering"() {
|
||||
setup: "create trace"
|
||||
def clock = new Clock(tracer)
|
||||
def parentContext = new SpanContextImpl("123", "456", "789")
|
||||
def trace = new TraceImpl(tracer, parentContext, clock.createCurrentTimestamp())
|
||||
trace.getRootSpan().setResource("test resource")
|
||||
trace.getRootSpan().setType("test type")
|
||||
trace.getRootSpan().setName("test name")
|
||||
trace.getRootSpan().setMeta("number.key", 123)
|
||||
trace.getRootSpan().setMeta("string.key", "meta string")
|
||||
trace.getRootSpan().setMeta("boolean.key", true)
|
||||
|
||||
def childSpan = trace.createSpan(trace.getRootSpan().getContext())
|
||||
childSpan.setResource("child span test resource")
|
||||
childSpan.setType("child span test type")
|
||||
childSpan.setName("child span test name")
|
||||
childSpan.setMeta("child.span.number.key", 123)
|
||||
childSpan.setMeta("child.span.string.key", "meta string")
|
||||
childSpan.setMeta("child.span.boolean.key", true)
|
||||
childSpan.finish()
|
||||
|
||||
trace.getRootSpan().finish()
|
||||
|
||||
when: "convert to JSON"
|
||||
def string = objectMapper.writeValueAsString(trace)
|
||||
def parsedTrace = objectMapper.readValue(string, new TypeReference<List<JsonSpan>>() {})
|
||||
|
||||
then:
|
||||
parsedTrace == [new JsonSpan(childSpan), new JsonSpan(trace.getRootSpan())]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue