Skip to content

Commit 79c9a04

Browse files
authored
feat: adds support for exporting spans in otlp format (#62)
* feat: adds support for exporting spans in otlp format * refactor: addressed review comments and refactor a bit * feat: removed un-used protobuf plugin * chore: addressed missed review comments * refactor: removed the request scope argument from span request builder * fix: extending expiry date for IONETTY-1042268 * test: adds the unit test for export span conversion * refactor the tags based method * chore: address few comments
1 parent ad3beb6 commit 79c9a04

File tree

17 files changed

+657
-14
lines changed

17 files changed

+657
-14
lines changed

.snyk

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ ignore:
55
SNYK-JAVA-IONETTY-1042268:
66
- '*':
77
reason: No replacement available
8-
expires: 2021-04-31T00:00:00.000Z
8+
expires: 2021-06-30T00:00:00.000Z
99
patch: {}
1010

hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/request/DefaultResultSetRequestBuilder.java

+48
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import graphql.schema.DataFetchingFieldSelectionSet;
66
import graphql.schema.SelectedField;
7+
import io.reactivex.rxjava3.core.Observable;
78
import io.reactivex.rxjava3.core.Single;
89
import java.util.Collection;
910
import java.util.Collections;
@@ -143,6 +144,53 @@ public <O extends OrderArgument> Single<ResultSetRequest<O>> build(
143144
spaceId));
144145
}
145146

147+
@Override
148+
public Single<ResultSetRequest<OrderArgument>> build(
149+
GraphQlRequestContext context,
150+
String requestScope,
151+
Map<String, Object> arguments,
152+
List<String> attributes) {
153+
int limit =
154+
this.argumentDeserializer
155+
.deserializePrimitive(arguments, LimitArgument.class)
156+
.orElse(DEFAULT_LIMIT);
157+
158+
TimeRangeArgument timeRange =
159+
this.argumentDeserializer
160+
.deserializeObject(arguments, TimeRangeArgument.class)
161+
.orElseThrow();
162+
163+
List<FilterArgument> requestedFilters =
164+
this.argumentDeserializer
165+
.deserializeObjectList(arguments, FilterArgument.class)
166+
.orElse(Collections.emptyList());
167+
168+
return zip(
169+
this.getAttributeRequests(context, requestScope, attributes).collect(Collectors.toList()),
170+
this.attributeRequestBuilder.buildForId(context, requestScope),
171+
this.filterRequestBuilder.build(context, requestScope, requestedFilters),
172+
(attributeRequests, idAttribute, filters) ->
173+
new DefaultResultSetRequest<>(
174+
context,
175+
attributeRequests,
176+
idAttribute,
177+
timeRange,
178+
limit,
179+
0,
180+
List.of(),
181+
filters,
182+
Optional.empty()));
183+
}
184+
185+
private Observable<AttributeRequest> getAttributeRequests(
186+
GraphQlRequestContext context, String requestScope, List<String> attributes) {
187+
return Observable.fromIterable(attributes)
188+
.distinct()
189+
.flatMapSingle(
190+
attributeKey ->
191+
this.attributeRequestBuilder.buildForKey(context, requestScope, attributeKey));
192+
}
193+
146194
private Stream<SelectedField> getAttributeQueryableFields(
147195
DataFetchingFieldSelectionSet selectionSet) {
148196
return this.selectionFinder.findSelections(

hypertrace-core-graphql-common-schema/src/main/java/org/hypertrace/core/graphql/common/request/ResultSetRequestBuilder.java

+6
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,10 @@ <O extends OrderArgument> Single<ResultSetRequest<O>> build(
3737
Collection<AttributeAssociation<FilterArgument>> filterArguments,
3838
Stream<SelectedField> attributeQueryableFields,
3939
Optional<String> spaceId);
40+
41+
Single<ResultSetRequest<OrderArgument>> build(
42+
GraphQlRequestContext context,
43+
String requestScope,
44+
Map<String, Object> arguments,
45+
List<String> attributes);
4046
}

hypertrace-core-graphql-platform/build.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
api("org.slf4j:slf4j-api:1.7.30")
1717
api("io.reactivex.rxjava3:rxjava:3.0.9")
1818
api("com.google.protobuf:protobuf-java-util:3.14.0")
19+
1920
api("org.projectlombok:lombok:1.18.18")
2021
api("com.google.code.findbugs:jsr305:3.0.2")
2122
api("com.typesafe:config:1.4.1")
@@ -27,6 +28,8 @@ dependencies {
2728
api("io.grpc:grpc-context:1.36.0")
2829
api("com.fasterxml.jackson.core:jackson-databind:2.12.1")
2930
api("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.1")
31+
api("org.apache.commons:commons-text:1.9")
32+
api("io.opentelemetry:opentelemetry-proto:1.1.0-alpha")
3033

3134
runtime("org.apache.logging.log4j:log4j-slf4j-impl:2.14.0")
3235
runtime("io.grpc:grpc-netty:1.37.0")

hypertrace-core-graphql-span-schema/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ dependencies {
1717
implementation("io.reactivex.rxjava3:rxjava")
1818
implementation("org.hypertrace.gateway.service:gateway-service-api")
1919
implementation("com.google.protobuf:protobuf-java-util")
20+
implementation("io.opentelemetry:opentelemetry-proto")
21+
implementation("org.apache.commons:commons-text")
2022

2123
implementation(project(":hypertrace-core-graphql-context"))
2224
implementation(project(":hypertrace-core-graphql-grpc-utils"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.hypertrace.core.graphql.span.dao;
2+
3+
import io.reactivex.rxjava3.core.Single;
4+
import java.util.List;
5+
import java.util.stream.Collectors;
6+
import javax.inject.Inject;
7+
import lombok.experimental.Accessors;
8+
import org.hypertrace.core.graphql.span.export.ExportSpan;
9+
import org.hypertrace.core.graphql.span.export.ExportSpanConverter;
10+
import org.hypertrace.core.graphql.span.request.SpanRequest;
11+
import org.hypertrace.core.graphql.span.schema.ExportSpanResult;
12+
import org.hypertrace.core.graphql.span.schema.SpanResultSet;
13+
14+
public class ExportSpanDao {
15+
private final SpanDao spanDao;
16+
17+
@Inject
18+
ExportSpanDao(SpanDao spanDao) {
19+
this.spanDao = spanDao;
20+
}
21+
22+
public Single<ExportSpanResult> getSpans(SpanRequest request) {
23+
return this.spanDao
24+
.getSpans(request)
25+
.flatMap(spanResultSet -> this.buildResponse(spanResultSet));
26+
}
27+
28+
private Single<ExportSpanResult> buildResponse(SpanResultSet result) throws Exception {
29+
List<ExportSpan> exportSpans =
30+
result.results().stream()
31+
.map(span -> new ExportSpan.Builder(span).build())
32+
.collect(Collectors.toList());
33+
return Single.just(new ExportSpanResultImpl(ExportSpanConverter.toJson(exportSpans)));
34+
}
35+
36+
@lombok.Value
37+
@Accessors(fluent = true)
38+
private static class ExportSpanResultImpl implements ExportSpanResult {
39+
String result;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package org.hypertrace.core.graphql.span.export;
2+
3+
import static org.hypertrace.core.graphql.span.export.ExportSpanConstants.SpanTagsKey.SERVICE_NAME_KEY;
4+
import static org.hypertrace.core.graphql.span.export.ExportSpanConstants.SpanTagsKey.SPAN_KIND;
5+
6+
import com.google.common.io.BaseEncoding;
7+
import com.google.protobuf.ByteString;
8+
import io.opentelemetry.proto.common.v1.AnyValue;
9+
import io.opentelemetry.proto.common.v1.KeyValue;
10+
import io.opentelemetry.proto.resource.v1.Resource;
11+
import io.opentelemetry.proto.trace.v1.InstrumentationLibrarySpans;
12+
import io.opentelemetry.proto.trace.v1.ResourceSpans;
13+
import io.opentelemetry.proto.trace.v1.Span;
14+
import io.opentelemetry.proto.trace.v1.Span.SpanKind;
15+
import io.opentelemetry.proto.trace.v1.Status;
16+
import io.opentelemetry.proto.trace.v1.Status.StatusCode;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.concurrent.TimeUnit;
20+
import java.util.stream.Collectors;
21+
import lombok.AccessLevel;
22+
import lombok.RequiredArgsConstructor;
23+
import lombok.experimental.Accessors;
24+
import org.hypertrace.core.graphql.span.export.ExportSpanConstants.SpanAttributes;
25+
import org.hypertrace.core.graphql.span.export.ExportSpanConstants.SpanTagsKey;
26+
27+
@lombok.Value
28+
@Accessors(fluent = true)
29+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
30+
public class ExportSpan {
31+
32+
private final ResourceSpans resourceSpans;
33+
34+
public static class Builder {
35+
36+
private final org.hypertrace.core.graphql.span.schema.Span span;
37+
38+
public Builder(org.hypertrace.core.graphql.span.schema.Span span) {
39+
this.span = span;
40+
}
41+
42+
private void setResourceServiceName(Resource.Builder resourceBuilder) {
43+
if (span.attribute(SpanAttributes.SERVICE_NAME) != null) {
44+
String serviceName = span.attribute(SpanAttributes.SERVICE_NAME).toString();
45+
KeyValue keyValue =
46+
KeyValue.newBuilder()
47+
.setKey(SERVICE_NAME_KEY)
48+
.setValue(AnyValue.newBuilder().setStringValue(serviceName).build())
49+
.build();
50+
resourceBuilder.addAttributes(keyValue);
51+
}
52+
}
53+
54+
private void setBytesFields(Span.Builder spanBuilder) {
55+
byte[] spanIdBytes = BaseEncoding.base64().decode(span.id());
56+
spanBuilder.setSpanId(ByteString.copyFrom(spanIdBytes));
57+
58+
String traceId = span.attribute(SpanAttributes.TRACE_ID).toString();
59+
byte[] traceIdBytes = BaseEncoding.base64().decode(traceId);
60+
spanBuilder.setTraceId(ByteString.copyFrom(traceIdBytes));
61+
62+
byte[] parentSpanIdBytes = BaseEncoding.base64().decode("");
63+
if (span.attribute(SpanAttributes.PARENT_SPAN_ID) != null) {
64+
parentSpanIdBytes =
65+
BaseEncoding.base64().decode(span.attribute(SpanAttributes.PARENT_SPAN_ID).toString());
66+
}
67+
spanBuilder.setParentSpanId(ByteString.copyFrom(parentSpanIdBytes));
68+
}
69+
70+
private void setTimeFields(Span.Builder spanBuilder) {
71+
long startTime = Long.parseLong(span.attribute(SpanAttributes.START_TIME).toString());
72+
spanBuilder.setStartTimeUnixNano(
73+
TimeUnit.NANOSECONDS.convert(startTime, TimeUnit.MILLISECONDS));
74+
75+
long endTime = Long.parseLong(span.attribute(SpanAttributes.END_TIME).toString());
76+
spanBuilder.setEndTimeUnixNano(TimeUnit.NANOSECONDS.convert(endTime, TimeUnit.MILLISECONDS));
77+
}
78+
79+
private void setName(Span.Builder spanBuilder) {
80+
if (span.attribute(SpanAttributes.NAME) != null) {
81+
spanBuilder.setName(span.attribute(SpanAttributes.NAME).toString());
82+
}
83+
}
84+
85+
private static void setAttributes(Span.Builder spanBuilder, Map<String, String> tags) {
86+
List<KeyValue> attributes =
87+
tags.entrySet().stream()
88+
.filter(e -> !SpanTagsKey.getExcludeKeys().contains(e.getKey()))
89+
.map(
90+
e ->
91+
KeyValue.newBuilder()
92+
.setKey(e.getKey())
93+
.setValue(AnyValue.newBuilder().setStringValue(e.getValue()).build())
94+
.build())
95+
.collect(Collectors.toList());
96+
spanBuilder.addAllAttributes(attributes);
97+
}
98+
99+
private static void setStatusCode(Span.Builder spanBuilder, Map<String, String> tags) {
100+
int statusCode =
101+
SpanTagsKey.getStatusCodeKeys().stream()
102+
.filter(e -> tags.containsKey(e))
103+
.map(e -> Integer.parseInt(tags.get(e)))
104+
.findFirst()
105+
.orElse(0);
106+
spanBuilder.setStatus(Status.newBuilder().setCode(StatusCode.forNumber(statusCode)).build());
107+
}
108+
109+
private static void setSpanKind(Span.Builder spanBuilder, Map<String, String> tags) {
110+
String spanKind = tags.get(SPAN_KIND);
111+
if (spanKind != null) {
112+
spanBuilder.setKind(
113+
SpanKind.valueOf(String.join("_", "SPAN_KIND", spanKind.toUpperCase())));
114+
} else {
115+
spanBuilder.setKind(SpanKind.SPAN_KIND_UNSPECIFIED);
116+
}
117+
}
118+
119+
public ExportSpan build() {
120+
121+
ResourceSpans.Builder resourceSpansBuilder = ResourceSpans.newBuilder();
122+
Resource.Builder resourceBuilder = Resource.newBuilder();
123+
InstrumentationLibrarySpans.Builder instrumentationLibrarySpansBuilder =
124+
InstrumentationLibrarySpans.newBuilder();
125+
Span.Builder spanBuilder = Span.newBuilder();
126+
127+
setResourceServiceName(resourceBuilder);
128+
setBytesFields(spanBuilder);
129+
setTimeFields(spanBuilder);
130+
setName(spanBuilder);
131+
132+
Map<String, String> tags =
133+
span.attribute(SpanAttributes.TAGS) != null
134+
? (Map<String, String>) span.attribute(SpanAttributes.TAGS)
135+
: Map.of();
136+
setStatusCode(spanBuilder, tags);
137+
setSpanKind(spanBuilder, tags);
138+
setAttributes(spanBuilder, tags);
139+
140+
resourceSpansBuilder.setResource(resourceBuilder.build());
141+
instrumentationLibrarySpansBuilder.addSpans(spanBuilder.build());
142+
resourceSpansBuilder.addInstrumentationLibrarySpans(
143+
instrumentationLibrarySpansBuilder.build());
144+
145+
return new ExportSpan(resourceSpansBuilder.build());
146+
}
147+
}
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.hypertrace.core.graphql.span.export;
2+
3+
import java.util.List;
4+
5+
public interface ExportSpanConstants {
6+
interface SpanAttributes {
7+
String ID = "id";
8+
String SERVICE_NAME = "serviceName";
9+
String TRACE_ID = "traceId";
10+
String PARENT_SPAN_ID = "parentSpanId";
11+
String START_TIME = "startTime";
12+
String END_TIME = "endTime";
13+
String NAME = "displaySpanName";
14+
String TAGS = "spanTags";
15+
16+
static List<String> getSpanAttributes() {
17+
return List.of(
18+
SpanAttributes.ID,
19+
SpanAttributes.SERVICE_NAME,
20+
SpanAttributes.TRACE_ID,
21+
SpanAttributes.PARENT_SPAN_ID,
22+
SpanAttributes.START_TIME,
23+
SpanAttributes.END_TIME,
24+
SpanAttributes.NAME,
25+
SpanAttributes.TAGS);
26+
}
27+
}
28+
29+
interface SpanTagsKey {
30+
String SERVICE_NAME_KEY = "service.name";
31+
String SPAN_KIND = "span.kind";
32+
String STATUS_CODE = "status.code";
33+
String ERROR = "error";
34+
35+
static List<String> getExcludeKeys() {
36+
return List.of(SPAN_KIND, STATUS_CODE, ERROR);
37+
}
38+
39+
static List<String> getStatusCodeKeys() {
40+
return List.of(STATUS_CODE, ERROR);
41+
}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.hypertrace.core.graphql.span.export;
2+
3+
import com.fasterxml.jackson.core.JsonGenerator;
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import com.fasterxml.jackson.databind.JsonSerializer;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.SerializerProvider;
9+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
10+
import com.google.protobuf.Message;
11+
import com.google.protobuf.util.JsonFormat;
12+
import io.opentelemetry.proto.trace.v1.ResourceSpans;
13+
import java.io.IOException;
14+
import java.util.List;
15+
import java.util.stream.Collectors;
16+
import lombok.experimental.Accessors;
17+
import org.apache.commons.text.StringEscapeUtils;
18+
19+
@lombok.Value
20+
@Accessors(fluent = true)
21+
public class ExportSpanConverter {
22+
23+
private static final ObjectMapper OBJECT_MAPPER =
24+
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
25+
26+
private static class MessageSerializer extends JsonSerializer<Message> {
27+
private static final JsonFormat.Printer PRINTER =
28+
JsonFormat.printer().omittingInsignificantWhitespace();
29+
30+
@Override
31+
public void serialize(Message message, JsonGenerator generator, SerializerProvider serializers)
32+
throws IOException {
33+
generator.writeRawValue(PRINTER.print(message));
34+
}
35+
}
36+
37+
@JsonSerialize(contentUsing = MessageSerializer.class)
38+
List<ResourceSpans> resourceSpans;
39+
40+
private String toJson() throws JsonProcessingException {
41+
return StringEscapeUtils.unescapeJson(OBJECT_MAPPER.writeValueAsString(this));
42+
}
43+
44+
public static String toJson(List<ExportSpan> exportSpans) throws JsonProcessingException {
45+
List<ResourceSpans> spans =
46+
exportSpans.stream().map(s -> s.resourceSpans()).collect(Collectors.toList());
47+
return new ExportSpanConverter(spans).toJson();
48+
}
49+
}

0 commit comments

Comments
 (0)