In order to allow using this exporter as an embedded exporter in an application. Adds a new internal `transform` package to transform spans from the `[]sdktrace.ReadOnlySpan` structure to `ptrace.Traces`.main v0.1.0-rc2
parent
95dea9dfde
commit
4f95e09116
@ -0,0 +1,166 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/fnv"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/collector/pdata/pcommon"
|
||||||
|
"go.opentelemetry.io/collector/pdata/ptrace"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Spans(sdl []sdktrace.ReadOnlySpan) ptrace.Traces {
|
||||||
|
traces := ptrace.NewTraces()
|
||||||
|
rss := traces.ResourceSpans()
|
||||||
|
resMap := make(map[uint64]ptrace.ResourceSpans)
|
||||||
|
scopeMap := make(map[uint64]ptrace.ScopeSpans)
|
||||||
|
|
||||||
|
for _, s := range sdl {
|
||||||
|
var rs ptrace.ResourceSpans
|
||||||
|
if r, ok := resMap[hashResource(s.Resource())]; ok {
|
||||||
|
rs = r
|
||||||
|
} else {
|
||||||
|
// create a new resource
|
||||||
|
// append it to the traces
|
||||||
|
// add it to the map
|
||||||
|
rs = rss.AppendEmpty()
|
||||||
|
resMap[hashResource(s.Resource())] = rs
|
||||||
|
}
|
||||||
|
|
||||||
|
res := rs.Resource()
|
||||||
|
res.SetDroppedAttributesCount(0) // TODO(wperron) how can we get this number?
|
||||||
|
ra := transformAttributes(s.Resource().Attributes())
|
||||||
|
ra.CopyTo(res.Attributes())
|
||||||
|
|
||||||
|
var ss ptrace.ScopeSpans
|
||||||
|
if scope, ok := scopeMap[hashScope(s.InstrumentationScope())]; ok {
|
||||||
|
ss = scope
|
||||||
|
} else {
|
||||||
|
// create a new scope
|
||||||
|
// append it to the resource
|
||||||
|
// add it to the map
|
||||||
|
ss = rs.ScopeSpans().AppendEmpty()
|
||||||
|
scopeMap[hashScope(s.InstrumentationScope())] = ss
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new span and fill it with the info from the readonly span
|
||||||
|
span := ss.Spans().AppendEmpty()
|
||||||
|
span.SetTraceID(pcommon.TraceID(s.SpanContext().TraceID()))
|
||||||
|
span.SetSpanID(pcommon.SpanID(s.SpanContext().SpanID()))
|
||||||
|
span.SetParentSpanID(pcommon.SpanID(s.Parent().SpanID()))
|
||||||
|
span.TraceState().FromRaw(s.SpanContext().TraceState().String())
|
||||||
|
span.SetName(s.Name())
|
||||||
|
span.SetKind(ptrace.SpanKind(s.SpanKind()))
|
||||||
|
span.SetStartTimestamp(pcommon.NewTimestampFromTime(s.StartTime()))
|
||||||
|
span.SetEndTimestamp(pcommon.NewTimestampFromTime(s.EndTime()))
|
||||||
|
var code ptrace.StatusCode
|
||||||
|
switch s.Status().Code {
|
||||||
|
case codes.Unset:
|
||||||
|
code = ptrace.StatusCodeUnset
|
||||||
|
case codes.Ok:
|
||||||
|
code = ptrace.StatusCodeOk
|
||||||
|
case codes.Error:
|
||||||
|
code = ptrace.StatusCodeError
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
span.Status().SetCode(code)
|
||||||
|
span.Status().SetMessage(s.Status().Description)
|
||||||
|
span.SetDroppedAttributesCount(uint32(s.DroppedAttributes()))
|
||||||
|
span.SetDroppedEventsCount(uint32(s.DroppedEvents()))
|
||||||
|
span.SetDroppedLinksCount(uint32(s.DroppedLinks()))
|
||||||
|
|
||||||
|
a := transformAttributes(s.Attributes())
|
||||||
|
a.CopyTo(span.Attributes())
|
||||||
|
|
||||||
|
for _, e := range s.Events() {
|
||||||
|
ev := span.Events().AppendEmpty()
|
||||||
|
ev.SetTimestamp(pcommon.NewTimestampFromTime(e.Time))
|
||||||
|
ev.SetName(e.Name)
|
||||||
|
ev.SetDroppedAttributesCount(uint32(e.DroppedAttributeCount))
|
||||||
|
ea := transformAttributes(e.Attributes)
|
||||||
|
ea.CopyTo(ev.Attributes())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range s.Links() {
|
||||||
|
ln := span.Links().AppendEmpty()
|
||||||
|
ln.SetTraceID(pcommon.TraceID(l.SpanContext.TraceID()))
|
||||||
|
ln.SetSpanID(pcommon.SpanID(l.SpanContext.SpanID()))
|
||||||
|
ln.TraceState().FromRaw(l.SpanContext.TraceState().String())
|
||||||
|
ln.SetDroppedAttributesCount(uint32(l.DroppedAttributeCount))
|
||||||
|
la := transformAttributes(l.Attributes)
|
||||||
|
la.CopyTo(ln.Attributes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformAttributes(from []attribute.KeyValue) pcommon.Map {
|
||||||
|
to := pcommon.NewMap()
|
||||||
|
to.EnsureCapacity(len(from))
|
||||||
|
|
||||||
|
for _, a := range from {
|
||||||
|
switch a.Value.Type() {
|
||||||
|
case attribute.BOOL:
|
||||||
|
to.PutBool(string(a.Key), a.Value.AsBool())
|
||||||
|
case attribute.INT64:
|
||||||
|
to.PutInt(string(a.Key), a.Value.AsInt64())
|
||||||
|
case attribute.FLOAT64:
|
||||||
|
to.PutDouble(string(a.Key), a.Value.AsFloat64())
|
||||||
|
case attribute.STRING:
|
||||||
|
to.PutStr(string(a.Key), a.Value.AsString())
|
||||||
|
case attribute.BOOLSLICE:
|
||||||
|
s := to.PutEmptySlice(string(a.Key))
|
||||||
|
raw := a.Value.AsBoolSlice()
|
||||||
|
s.EnsureCapacity(len(raw))
|
||||||
|
for _, r := range raw {
|
||||||
|
v := s.AppendEmpty()
|
||||||
|
v.SetBool(r)
|
||||||
|
}
|
||||||
|
case attribute.INT64SLICE:
|
||||||
|
s := to.PutEmptySlice(string(a.Key))
|
||||||
|
raw := a.Value.AsInt64Slice()
|
||||||
|
s.EnsureCapacity(len(raw))
|
||||||
|
for _, r := range raw {
|
||||||
|
v := s.AppendEmpty()
|
||||||
|
v.SetInt(r)
|
||||||
|
}
|
||||||
|
case attribute.STRINGSLICE:
|
||||||
|
s := to.PutEmptySlice(string(a.Key))
|
||||||
|
raw := a.Value.AsStringSlice()
|
||||||
|
s.EnsureCapacity(len(raw))
|
||||||
|
for _, r := range raw {
|
||||||
|
v := s.AppendEmpty()
|
||||||
|
v.SetStr(r)
|
||||||
|
}
|
||||||
|
case attribute.FLOAT64SLICE:
|
||||||
|
s := to.PutEmptySlice(string(a.Key))
|
||||||
|
raw := a.Value.AsFloat64Slice()
|
||||||
|
s.EnsureCapacity(len(raw))
|
||||||
|
for _, r := range raw {
|
||||||
|
v := s.AppendEmpty()
|
||||||
|
v.SetDouble(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return to
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashResource(res *resource.Resource) uint64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write([]byte(res.Encoded(attribute.DefaultEncoder())))
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashScope(s instrumentation.Scope) uint64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write([]byte(s.Name))
|
||||||
|
h.Write([]byte(s.SchemaURL))
|
||||||
|
h.Write([]byte(s.Version))
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
@ -0,0 +1,234 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.opentelemetry.io/collector/pdata/pcommon"
|
||||||
|
"go.opentelemetry.io/collector/pdata/ptrace"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTransformSpan(t *testing.T) {
|
||||||
|
sdkspanslice := make([]sdktrace.ReadOnlySpan, 1)
|
||||||
|
start := time.Unix(1000, 0)
|
||||||
|
end := start.Add(5 * time.Second)
|
||||||
|
evtts := time.Unix(1500, 0)
|
||||||
|
|
||||||
|
stringVals := []string{"first", "second"}
|
||||||
|
intVals := []int{1, 2, 3}
|
||||||
|
floatVals := []float64{1.1, 2.2, 3.3}
|
||||||
|
boolVals := []bool{true, false}
|
||||||
|
|
||||||
|
sdkspanslice[0] = tracetest.SpanStub{
|
||||||
|
Name: "span-stub",
|
||||||
|
SpanContext: trace.SpanContext{}.
|
||||||
|
WithSpanID(trace.SpanID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}).
|
||||||
|
WithTraceID(trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}).
|
||||||
|
WithTraceState(must(trace.ParseTraceState("foo=bar"))),
|
||||||
|
Parent: trace.SpanContext{}.
|
||||||
|
WithSpanID(trace.SpanID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11}).
|
||||||
|
WithTraceID(trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}).
|
||||||
|
WithTraceState(must(trace.ParseTraceState("foo=bar"))),
|
||||||
|
SpanKind: trace.SpanKindServer,
|
||||||
|
StartTime: start,
|
||||||
|
EndTime: end,
|
||||||
|
Attributes: []attribute.KeyValue{
|
||||||
|
{
|
||||||
|
Key: "stringkey",
|
||||||
|
Value: attribute.StringValue("stringval"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "intkey",
|
||||||
|
Value: attribute.IntValue(123),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "floatkey",
|
||||||
|
Value: attribute.Float64Value(111.2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "boolkey",
|
||||||
|
Value: attribute.BoolValue(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "stringslicekey",
|
||||||
|
Value: attribute.StringSliceValue(stringVals),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "intslicekey",
|
||||||
|
Value: attribute.IntSliceValue(intVals),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "floatslicekey",
|
||||||
|
Value: attribute.Float64SliceValue(floatVals),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "boolslicekey",
|
||||||
|
Value: attribute.BoolSliceValue(boolVals),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Events: []sdktrace.Event{
|
||||||
|
{
|
||||||
|
Name: "spanevent",
|
||||||
|
Attributes: []attribute.KeyValue{
|
||||||
|
{
|
||||||
|
Key: "eventstringkey",
|
||||||
|
Value: attribute.StringValue("eventstringval"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DroppedAttributeCount: 4,
|
||||||
|
Time: evtts,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Links: []sdktrace.Link{
|
||||||
|
{
|
||||||
|
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
|
||||||
|
TraceID: [16]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
|
||||||
|
SpanID: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
|
||||||
|
TraceFlags: 1,
|
||||||
|
TraceState: must(trace.ParseTraceState("foo=bar")),
|
||||||
|
Remote: false,
|
||||||
|
}),
|
||||||
|
Attributes: []attribute.KeyValue{
|
||||||
|
{
|
||||||
|
Key: "linkstringkey",
|
||||||
|
Value: attribute.StringValue("linkstringval"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DroppedAttributeCount: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: sdktrace.Status{
|
||||||
|
Code: codes.Ok,
|
||||||
|
Description: "OK",
|
||||||
|
},
|
||||||
|
DroppedAttributes: 1,
|
||||||
|
DroppedEvents: 2,
|
||||||
|
DroppedLinks: 3,
|
||||||
|
ChildSpanCount: 0,
|
||||||
|
Resource: resource.NewWithAttributes("https://opentelemetry.io/schemas/1.24.0",
|
||||||
|
attribute.KeyValue{Key: "service.name", Value: attribute.StringValue("test-service")},
|
||||||
|
),
|
||||||
|
InstrumentationLibrary: instrumentation.Scope{
|
||||||
|
Name: "test-tracer",
|
||||||
|
Version: "0.0.1",
|
||||||
|
SchemaURL: "https://opentelemetry.io/schemas/1.24.0",
|
||||||
|
},
|
||||||
|
}.Snapshot()
|
||||||
|
|
||||||
|
spans := Spans(sdkspanslice)
|
||||||
|
assert.Equal(t, 1, spans.ResourceSpans().Len())
|
||||||
|
|
||||||
|
for i := 0; i < spans.ResourceSpans().Len(); i++ {
|
||||||
|
require.Less(t, i, 1)
|
||||||
|
rs := spans.ResourceSpans().At(i)
|
||||||
|
res := rs.Resource()
|
||||||
|
exp := pcommon.NewMap()
|
||||||
|
exp.PutStr("service.name", "test-service")
|
||||||
|
assert.Equal(t, exp, res.Attributes())
|
||||||
|
assert.Equal(t, uint32(0), res.DroppedAttributesCount())
|
||||||
|
|
||||||
|
for j := 0; j < rs.ScopeSpans().Len(); j++ {
|
||||||
|
require.Less(t, j, 1)
|
||||||
|
ss := rs.ScopeSpans().At(j)
|
||||||
|
for k := 0; k < ss.Spans().Len(); k++ {
|
||||||
|
require.Less(t, k, 1)
|
||||||
|
span := ss.Spans().At(k)
|
||||||
|
|
||||||
|
ts := pcommon.NewTraceState()
|
||||||
|
ts.FromRaw("foo=bar")
|
||||||
|
assert.Equal(t, pcommon.SpanID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, span.SpanID())
|
||||||
|
assert.Equal(t, pcommon.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, span.TraceID())
|
||||||
|
assert.Equal(t, pcommon.SpanID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11}, span.ParentSpanID())
|
||||||
|
assert.Equal(t, ts, span.TraceState())
|
||||||
|
assert.Equal(t, "span-stub", span.Name())
|
||||||
|
assert.Equal(t, ptrace.SpanKindServer, span.Kind())
|
||||||
|
assert.Equal(t, ptrace.StatusCodeOk, span.Status().Code())
|
||||||
|
assert.Equal(t, "OK", span.Status().Message())
|
||||||
|
assert.Equal(t, pcommon.NewTimestampFromTime(start), span.StartTimestamp())
|
||||||
|
assert.Equal(t, pcommon.NewTimestampFromTime(end), span.EndTimestamp())
|
||||||
|
assert.Equal(t, uint32(1), span.DroppedAttributesCount())
|
||||||
|
assert.Equal(t, uint32(2), span.DroppedEventsCount())
|
||||||
|
assert.Equal(t, uint32(3), span.DroppedLinksCount())
|
||||||
|
|
||||||
|
a, ok := span.Attributes().Get("stringkey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "stringval", a.Str())
|
||||||
|
|
||||||
|
a, ok = span.Attributes().Get("intkey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, int64(123), a.Int())
|
||||||
|
|
||||||
|
a, ok = span.Attributes().Get("floatkey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, float64(111.2), a.Double())
|
||||||
|
|
||||||
|
a, ok = span.Attributes().Get("boolkey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, true, a.Bool())
|
||||||
|
|
||||||
|
a, ok = span.Attributes().Get("stringslicekey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
for i := 0; i < a.Slice().Len(); i++ {
|
||||||
|
v := a.Slice().At(i)
|
||||||
|
assert.Equal(t, stringVals[i], v.Str())
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok = span.Attributes().Get("intslicekey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
for i := 0; i < a.Slice().Len(); i++ {
|
||||||
|
v := a.Slice().At(i)
|
||||||
|
assert.Equal(t, int64(intVals[i]), v.Int())
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok = span.Attributes().Get("floatslicekey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
for i := 0; i < a.Slice().Len(); i++ {
|
||||||
|
v := a.Slice().At(i)
|
||||||
|
assert.Equal(t, floatVals[i], v.Double())
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok = span.Attributes().Get("boolslicekey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
for i := 0; i < a.Slice().Len(); i++ {
|
||||||
|
v := a.Slice().At(i)
|
||||||
|
assert.Equal(t, boolVals[i], v.Bool())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 1, span.Events().Len())
|
||||||
|
ev := span.Events().At(0)
|
||||||
|
assert.Equal(t, evtts.UTC(), ev.Timestamp().AsTime())
|
||||||
|
assert.Equal(t, "spanevent", ev.Name())
|
||||||
|
assert.Equal(t, uint32(4), ev.DroppedAttributesCount())
|
||||||
|
a, ok = ev.Attributes().Get("eventstringkey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "eventstringval", a.Str())
|
||||||
|
|
||||||
|
assert.Equal(t, 1, span.Links().Len())
|
||||||
|
ln := span.Links().At(0)
|
||||||
|
assert.Equal(t, pcommon.TraceID([16]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}), ln.TraceID())
|
||||||
|
assert.Equal(t, pcommon.SpanID([8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}), ln.SpanID())
|
||||||
|
assert.Equal(t, ts, ln.TraceState())
|
||||||
|
assert.Equal(t, uint32(5), ln.DroppedAttributesCount())
|
||||||
|
a, ok = ln.Attributes().Get("linkstringkey")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "linkstringval", a.Str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func must[T any](v T, err error) T {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
Loading…
Reference in new issue