Skip to content

Commit e7f41d2

Browse files
committed
[exporter/elasticsearch] Add OTel mapping mode for traces
1 parent c9bd3ef commit e7f41d2

File tree

4 files changed

+199
-71
lines changed

4 files changed

+199
-71
lines changed

exporter/elasticsearchexporter/exporter.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ func (e *elasticsearchExporter) pushTraceData(
344344
spans := scopeSpan.Spans()
345345
for k := 0; k < spans.Len(); k++ {
346346
span := spans.At(k)
347-
if err := e.pushTraceRecord(ctx, resource, span, scope, session); err != nil {
347+
if err := e.pushTraceRecord(ctx, resource, il.SchemaUrl(), span, scope, scopeSpan.SchemaUrl(), session); err != nil {
348348
if cerr := ctx.Err(); cerr != nil {
349349
return cerr
350350
}
@@ -366,8 +366,10 @@ func (e *elasticsearchExporter) pushTraceData(
366366
func (e *elasticsearchExporter) pushTraceRecord(
367367
ctx context.Context,
368368
resource pcommon.Resource,
369+
resourceSchemaURL string,
369370
span ptrace.Span,
370371
scope pcommon.InstrumentationScope,
372+
scopeSchemaURL string,
371373
bulkIndexerSession bulkIndexerSession,
372374
) error {
373375
fIndex := e.index
@@ -383,7 +385,7 @@ func (e *elasticsearchExporter) pushTraceRecord(
383385
fIndex = formattedIndex
384386
}
385387

386-
document, err := e.model.encodeSpan(resource, span, scope)
388+
document, err := e.model.encodeSpan(resource, resourceSchemaURL, span, scope, scopeSchemaURL)
387389
if err != nil {
388390
return fmt.Errorf("failed to encode trace record: %w", err)
389391
}

exporter/elasticsearchexporter/exporter_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,68 @@ func TestExporterTraces(t *testing.T) {
880880
))
881881
rec.WaitItems(1)
882882
})
883+
884+
t.Run("otel mode", func(t *testing.T) {
885+
rec := newBulkRecorder()
886+
server := newESTestServer(t, func(docs []itemRequest) ([]itemResponse, error) {
887+
rec.Record(docs)
888+
return itemsAllOK(docs)
889+
})
890+
891+
exporter := newTestTracesExporter(t, server.URL, func(cfg *Config) {
892+
cfg.TracesDynamicIndex.Enabled = true
893+
cfg.Mapping.Mode = "otel"
894+
})
895+
896+
traces := ptrace.NewTraces()
897+
resourceSpans := traces.ResourceSpans()
898+
rs := resourceSpans.AppendEmpty()
899+
900+
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
901+
span.SetName("name")
902+
span.SetTraceID(pcommon.NewTraceIDEmpty())
903+
span.SetSpanID(pcommon.NewSpanIDEmpty())
904+
span.SetFlags(1)
905+
span.SetDroppedAttributesCount(2)
906+
span.SetDroppedEventsCount(3)
907+
span.SetDroppedLinksCount(4)
908+
span.TraceState().FromRaw("foo")
909+
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Unix(3600, 0)))
910+
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Unix(7200, 0)))
911+
912+
scopeAttr := span.Attributes()
913+
fillResourceAttributeMap(scopeAttr, map[string]string{
914+
"attr.foo": "attr.bar",
915+
})
916+
917+
resAttr := rs.Resource().Attributes()
918+
fillResourceAttributeMap(resAttr, map[string]string{
919+
"resource.foo": "resource.bar",
920+
})
921+
922+
spanLink := span.Links().AppendEmpty()
923+
spanLink.SetTraceID(pcommon.NewTraceIDEmpty())
924+
spanLink.SetSpanID(pcommon.NewSpanIDEmpty())
925+
spanLink.SetFlags(10)
926+
spanLink.SetDroppedAttributesCount(11)
927+
spanLink.TraceState().FromRaw("bar")
928+
fillResourceAttributeMap(spanLink.Attributes(), map[string]string{
929+
"link.attr.foo": "link.attr.bar",
930+
})
931+
932+
mustSendTraces(t, exporter, traces)
933+
934+
rec.WaitItems(1)
935+
936+
expected := []itemRequest{
937+
{
938+
Action: []byte(`{"create":{"_index":"traces-generic.otel-default"}}`),
939+
Document: []byte(`{"@timestamp":"1970-01-01T01:00:00.000000000Z","attributes":{"attr.foo":"attr.bar"},"data_stream":{"dataset":"generic.otel","namespace":"default","type":"traces"},"dropped_attributes_count":2,"dropped_events_count":3,"dropped_links_count":4,"duration":3600000000000,"kind":"SPAN_KIND_UNSPECIFIED","links":[{"attributes":{"link.attr.foo":"link.attr.bar"},"dropped_attributes_count":11,"span_id":"","trace_flags":10,"trace_id":"","trace_state":"bar"}],"name":"name","resource":{"attributes":{"resource.foo":"resource.bar"},"dropped_attributes_count":0,"schema_url":""},"status":{"code":"Unset","message":""},"trace_flags":1,"trace_state":"foo"}`),
940+
},
941+
}
942+
943+
assertItemsEqual(t, expected, rec.Items(), false)
944+
})
883945
}
884946

885947
// TestExporterAuth verifies that the Elasticsearch exporter supports

exporter/elasticsearchexporter/model.go

Lines changed: 132 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ var resourceAttrsToPreserve = map[string]bool{
6565

6666
type mappingModel interface {
6767
encodeLog(pcommon.Resource, string, plog.LogRecord, pcommon.InstrumentationScope, string) ([]byte, error)
68-
encodeSpan(pcommon.Resource, ptrace.Span, pcommon.InstrumentationScope) ([]byte, error)
68+
encodeSpan(pcommon.Resource, string, ptrace.Span, pcommon.InstrumentationScope, string) ([]byte, error)
6969
upsertMetricDataPointValue(map[uint32]objmodel.Document, pcommon.Resource, pcommon.InstrumentationScope, pmetric.Metric, dataPoint, pcommon.Value) error
7070
encodeDocument(objmodel.Document) ([]byte, error)
7171
}
@@ -130,7 +130,29 @@ func (m *encodeModel) encodeLogDefaultMode(resource pcommon.Resource, record plo
130130
return document
131131
}
132132

133-
var datastreamKeys = []string{dataStreamType, dataStreamDataset, dataStreamNamespace}
133+
func forEachDataStreamKey(fn func(key string)) {
134+
for _, key := range []string{dataStreamType, dataStreamDataset, dataStreamNamespace} {
135+
fn(key)
136+
}
137+
}
138+
139+
// addDataStreamAttributes adds data_stream.* attributes to document
140+
func addDataStreamAttributes(document *objmodel.Document, attr pcommon.Map) {
141+
forEachDataStreamKey(func(key string) {
142+
if value, exists := attr.Get(key); exists {
143+
document.AddAttribute(key, value)
144+
}
145+
})
146+
}
147+
148+
// stripDataStreamAttributes removes data_stream.* attributes from map
149+
func stripDataStreamAttributes(attr pcommon.Map) {
150+
forEachDataStreamKey(func(key string) {
151+
if _, exists := attr.Get(key); exists {
152+
attr.Remove(key)
153+
}
154+
})
155+
}
134156

135157
func (m *encodeModel) encodeLogOTelMode(resource pcommon.Resource, resourceSchemaURL string, record plog.LogRecord, scope pcommon.InstrumentationScope, scopeSchemaURL string) objmodel.Document {
136158
var document objmodel.Document
@@ -154,67 +176,12 @@ func (m *encodeModel) encodeLogOTelMode(resource pcommon.Resource, resourceSchem
154176
// updated by the router.
155177
// Move them to the top of the document and remove them from the record
156178
attributeMap := record.Attributes()
157-
158-
forEachDataStreamKey := func(fn func(key string)) {
159-
for _, key := range datastreamKeys {
160-
fn(key)
161-
}
162-
}
163-
164-
forEachDataStreamKey(func(key string) {
165-
if value, exists := attributeMap.Get(key); exists {
166-
document.AddAttribute(key, value)
167-
attributeMap.Remove(key)
168-
}
169-
})
170-
179+
addDataStreamAttributes(&document, attributeMap)
180+
stripDataStreamAttributes(attributeMap)
171181
document.AddAttributes("attributes", attributeMap)
172182

173-
// Resource
174-
resourceMapVal := pcommon.NewValueMap()
175-
resourceMap := resourceMapVal.Map()
176-
resourceMap.PutStr("schema_url", resourceSchemaURL)
177-
resourceMap.PutInt("dropped_attributes_count", int64(resource.DroppedAttributesCount()))
178-
resourceAttrMap := resourceMap.PutEmptyMap("attributes")
179-
180-
resource.Attributes().CopyTo(resourceAttrMap)
181-
182-
// Remove data_stream attributes from the resources attributes if present
183-
forEachDataStreamKey(func(key string) {
184-
resourceAttrMap.Remove(key)
185-
})
186-
187-
document.Add("resource", objmodel.ValueFromAttribute(resourceMapVal))
188-
189-
// Scope
190-
scopeMapVal := pcommon.NewValueMap()
191-
scopeMap := scopeMapVal.Map()
192-
if scope.Name() != "" {
193-
scopeMap.PutStr("name", scope.Name())
194-
}
195-
if scope.Version() != "" {
196-
scopeMap.PutStr("version", scope.Version())
197-
}
198-
if scopeSchemaURL != "" {
199-
scopeMap.PutStr("schema_url", scopeSchemaURL)
200-
}
201-
if scope.DroppedAttributesCount() > 0 {
202-
scopeMap.PutInt("dropped_attributes_count", int64(scope.DroppedAttributesCount()))
203-
}
204-
scopeAttributes := scope.Attributes()
205-
if scopeAttributes.Len() > 0 {
206-
scopeAttrMap := scopeMap.PutEmptyMap("attributes")
207-
scopeAttributes.CopyTo(scopeAttrMap)
208-
209-
// Remove data_stream attributes from the scope attributes if present
210-
forEachDataStreamKey(func(key string) {
211-
scopeAttrMap.Remove(key)
212-
})
213-
}
214-
215-
if scopeMap.Len() > 0 {
216-
document.Add("scope", objmodel.ValueFromAttribute(scopeMapVal))
217-
}
183+
m.encodeResourceOTelMode(&document, resource, resourceSchemaURL)
184+
m.encodeScopeOTelMode(&document, scope, scopeSchemaURL)
218185

219186
// Body
220187
setOTelLogBody(&document, record.Body())
@@ -385,7 +352,109 @@ func numberToValue(dp pmetric.NumberDataPoint) (pcommon.Value, error) {
385352
return pcommon.Value{}, errInvalidNumberDataPoint
386353
}
387354

388-
func (m *encodeModel) encodeSpan(resource pcommon.Resource, span ptrace.Span, scope pcommon.InstrumentationScope) ([]byte, error) {
355+
func (m *encodeModel) encodeResourceOTelMode(document *objmodel.Document, resource pcommon.Resource, resourceSchemaURL string) {
356+
resourceMapVal := pcommon.NewValueMap()
357+
resourceMap := resourceMapVal.Map()
358+
resourceMap.PutStr("schema_url", resourceSchemaURL)
359+
resourceMap.PutInt("dropped_attributes_count", int64(resource.DroppedAttributesCount()))
360+
resourceAttrMap := resourceMap.PutEmptyMap("attributes")
361+
resource.Attributes().CopyTo(resourceAttrMap)
362+
stripDataStreamAttributes(resourceAttrMap)
363+
364+
document.Add("resource", objmodel.ValueFromAttribute(resourceMapVal))
365+
}
366+
367+
func (m *encodeModel) encodeScopeOTelMode(document *objmodel.Document, scope pcommon.InstrumentationScope, scopeSchemaURL string) {
368+
scopeMapVal := pcommon.NewValueMap()
369+
scopeMap := scopeMapVal.Map()
370+
if scope.Name() != "" {
371+
scopeMap.PutStr("name", scope.Name())
372+
}
373+
if scope.Version() != "" {
374+
scopeMap.PutStr("version", scope.Version())
375+
}
376+
if scopeSchemaURL != "" {
377+
scopeMap.PutStr("schema_url", scopeSchemaURL)
378+
}
379+
if scope.DroppedAttributesCount() > 0 {
380+
scopeMap.PutInt("dropped_attributes_count", int64(scope.DroppedAttributesCount()))
381+
}
382+
scopeAttributes := scope.Attributes()
383+
if scopeAttributes.Len() > 0 {
384+
scopeAttrMap := scopeMap.PutEmptyMap("attributes")
385+
scopeAttributes.CopyTo(scopeAttrMap)
386+
stripDataStreamAttributes(scopeAttrMap)
387+
}
388+
if scopeMap.Len() > 0 {
389+
document.Add("scope", objmodel.ValueFromAttribute(scopeMapVal))
390+
}
391+
}
392+
393+
func (m *encodeModel) encodeSpan(resource pcommon.Resource, resourceSchemaURL string, span ptrace.Span, scope pcommon.InstrumentationScope, scopeSchemaURL string) ([]byte, error) {
394+
var document objmodel.Document
395+
switch m.mode {
396+
case MappingOTel:
397+
document = m.encodeSpanOTelMode(resource, resourceSchemaURL, span, scope, scopeSchemaURL)
398+
default:
399+
document = m.encodeSpanDefaultMode(resource, span, scope)
400+
}
401+
document.Dedup()
402+
var buf bytes.Buffer
403+
err := document.Serialize(&buf, m.dedot, m.mode == MappingOTel)
404+
return buf.Bytes(), err
405+
}
406+
407+
func (m *encodeModel) encodeSpanOTelMode(resource pcommon.Resource, resourceSchemaURL string, span ptrace.Span, scope pcommon.InstrumentationScope, scopeSchemaURL string) objmodel.Document {
408+
var document objmodel.Document
409+
document.AddTimestamp("@timestamp", span.StartTimestamp())
410+
document.AddTraceID("trace_id", span.TraceID())
411+
document.AddSpanID("span_id", span.SpanID())
412+
document.AddString("trace_state", span.TraceState().AsRaw())
413+
document.AddSpanID("parent_span_id", span.ParentSpanID())
414+
document.AddInt("trace_flags", int64(span.Flags()))
415+
document.AddString("name", span.Name())
416+
document.AddString("kind", traceutil.SpanKindStr(span.Kind()))
417+
document.AddInt("duration", int64(span.EndTimestamp()-span.StartTimestamp()))
418+
419+
attributeMap := span.Attributes()
420+
addDataStreamAttributes(&document, attributeMap)
421+
stripDataStreamAttributes(attributeMap)
422+
document.AddAttributes("attributes", attributeMap)
423+
424+
document.AddInt("dropped_attributes_count", int64(span.DroppedAttributesCount()))
425+
document.AddInt("dropped_events_count", int64(span.DroppedEventsCount()))
426+
427+
links := pcommon.NewValueSlice()
428+
linkSlice := links.SetEmptySlice()
429+
spanLinks := span.Links()
430+
for i := 0; i < spanLinks.Len(); i++ {
431+
linkMap := linkSlice.AppendEmpty().SetEmptyMap()
432+
spanLink := spanLinks.At(i)
433+
linkMap.PutStr("trace_id", spanLink.TraceID().String())
434+
linkMap.PutStr("span_id", spanLink.SpanID().String())
435+
linkMap.PutStr("trace_state", spanLink.TraceState().AsRaw())
436+
mAttr := linkMap.PutEmptyMap("attributes")
437+
spanLink.Attributes().CopyTo(mAttr)
438+
linkMap.PutInt("dropped_attributes_count", int64(spanLink.DroppedAttributesCount()))
439+
linkMap.PutInt("trace_flags", int64(spanLink.Flags()))
440+
}
441+
document.AddAttribute("links", links)
442+
443+
document.AddInt("dropped_links_count", int64(span.DroppedLinksCount()))
444+
status := pcommon.NewMap()
445+
status.PutStr("message", span.Status().Message())
446+
status.PutStr("code", span.Status().Code().String())
447+
document.AddAttributes("status", status)
448+
449+
m.encodeResourceOTelMode(&document, resource, resourceSchemaURL)
450+
m.encodeScopeOTelMode(&document, scope, scopeSchemaURL)
451+
452+
// TODO: add span events to log data streams
453+
454+
return document
455+
}
456+
457+
func (m *encodeModel) encodeSpanDefaultMode(resource pcommon.Resource, span ptrace.Span, scope pcommon.InstrumentationScope) objmodel.Document {
389458
var document objmodel.Document
390459
document.AddTimestamp("@timestamp", span.StartTimestamp()) // We use @timestamp in order to ensure that we can index if the default data stream logs template is used.
391460
document.AddTimestamp("EndTimestamp", span.EndTimestamp())
@@ -402,12 +471,7 @@ func (m *encodeModel) encodeSpan(resource pcommon.Resource, span ptrace.Span, sc
402471
m.encodeEvents(&document, span.Events())
403472
document.AddInt("Duration", durationAsMicroseconds(span.StartTimestamp().AsTime(), span.EndTimestamp().AsTime())) // unit is microseconds
404473
document.AddAttributes("Scope", scopeToAttributes(scope))
405-
document.Dedup()
406-
407-
var buf bytes.Buffer
408-
// OTel serialization is not supported for traces yet
409-
err := document.Serialize(&buf, m.dedot, false)
410-
return buf.Bytes(), err
474+
return document
411475
}
412476

413477
func (m *encodeModel) encodeAttributes(document *objmodel.Document, attributes pcommon.Map) {

exporter/elasticsearchexporter/model_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var expectedLogBodyDeDottedWithEmptyTimestamp = `{"@timestamp":"1970-01-01T00:00
5454
func TestEncodeSpan(t *testing.T) {
5555
model := &encodeModel{dedot: false}
5656
td := mockResourceSpans()
57-
spanByte, err := model.encodeSpan(td.ResourceSpans().At(0).Resource(), td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0), td.ResourceSpans().At(0).ScopeSpans().At(0).Scope())
57+
spanByte, err := model.encodeSpan(td.ResourceSpans().At(0).Resource(), "", td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0), td.ResourceSpans().At(0).ScopeSpans().At(0).Scope(), "")
5858
assert.NoError(t, err)
5959
assert.Equal(t, expectedSpanBody, string(spanByte))
6060
}

0 commit comments

Comments
 (0)