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