From 4689b7c0cd669fd123d9fa12ebefcaf541be636f Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Tue, 1 Apr 2025 13:26:16 +0100 Subject: [PATCH] TraceView: Add scope attributes to span details (#103173) * Add scope attributes to span details * Tests --- pkg/tsdb/tempo/trace_transform.go | 23 +++++++- pkg/tsdb/tempo/trace_transform_test.go | 79 ++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/tempo/trace_transform.go b/pkg/tsdb/tempo/trace_transform.go index 971e9d1dd5d..7a5d6a0251b 100644 --- a/pkg/tsdb/tempo/trace_transform.go +++ b/pkg/tsdb/tempo/trace_transform.go @@ -143,7 +143,12 @@ func spanToSpanRow(span *tracev11.Span, libraryTags *commonv11.InstrumentationSc return nil, fmt.Errorf("failed to marshal service tags: %w", err) } - spanTags, err := json.Marshal(getSpanTags(span)) + // Get both span tags and scope tags and combine them + spanTagsList := getSpanTags(span) + scopeTagsList := getScopeTags(libraryTags) + spanTagsList = append(spanTagsList, scopeTagsList...) + + spanTags, err := json.Marshal(spanTagsList) if err != nil { return nil, fmt.Errorf("failed to marshal span tags: %w", err) } @@ -270,6 +275,22 @@ func getSpanTags(span *tracev11.Span) []*KeyValue { return tags } +func getScopeTags(scope *commonv11.InstrumentationScope) []*KeyValue { + if scope == nil || len(scope.Attributes) == 0 { + return nil + } + + tags := make([]*KeyValue, len(scope.Attributes)) + for i, attr := range scope.Attributes { + val, err := getAttributeVal(attr.Value) + if err != nil { + logger.Debug("error transforming scope attributes", "err", err) + } + tags[i] = &KeyValue{Key: attr.Key, Value: val} + } + return tags +} + func getSpanKind(spanKind tracev11.Span_SpanKind) string { var tagStr string switch spanKind { diff --git a/pkg/tsdb/tempo/trace_transform_test.go b/pkg/tsdb/tempo/trace_transform_test.go index 64cee9584a8..724960d0ace 100644 --- a/pkg/tsdb/tempo/trace_transform_test.go +++ b/pkg/tsdb/tempo/trace_transform_test.go @@ -10,6 +10,8 @@ import ( "github.com/golang/protobuf/jsonpb" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/tempo/pkg/tempopb" + commonv11 "github.com/grafana/tempo/pkg/tempopb/common/v1" + resourcev1 "github.com/grafana/tempo/pkg/tempopb/resource/v1" v1 "github.com/grafana/tempo/pkg/tempopb/trace/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -117,6 +119,83 @@ func TestTraceToFrame(t *testing.T) { }) } +func TestScopeAttributesAddedToTags(t *testing.T) { + span := &v1.Span{ + TraceId: []byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}, + SpanId: []byte{0, 1, 2, 3, 4, 5, 6, 7}, + ParentSpanId: []byte{0, 0, 0, 0, 0, 0, 0, 0}, + Name: "test-span", + StartTimeUnixNano: 1616072924070497000, + EndTimeUnixNano: 1616072924078918000, + Attributes: []*commonv11.KeyValue{ + { + Key: "span.attribute", + Value: &commonv11.AnyValue{ + Value: &commonv11.AnyValue_StringValue{ + StringValue: "span-value", + }, + }, + }, + }, + Status: &v1.Status{}, + } + + // Create instrumentation scope with attributes + scope := &commonv11.InstrumentationScope{ + Name: "my.library", + Version: "1.0.0", + Attributes: []*commonv11.KeyValue{ + { + Key: "scope.attribute", + Value: &commonv11.AnyValue{ + Value: &commonv11.AnyValue_StringValue{ + StringValue: "scope-value", + }, + }, + }, + }, + } + + resource := &resourcev1.Resource{ + Attributes: []*commonv11.KeyValue{ + { + Key: "service.name", + Value: &commonv11.AnyValue{ + Value: &commonv11.AnyValue_StringValue{ + StringValue: "test-service", + }, + }, + }, + }, + } + + row, err := spanToSpanRow(span, scope, resource) + require.NoError(t, err) + + // Check that both span and scope attributes are in tags + // Tags are at index 16 in the row (based on frame field ordering) + tagsJson := row[16].(json.RawMessage) + + var tags []*KeyValue + err = json.Unmarshal(tagsJson, &tags) + require.NoError(t, err) + + foundSpanAttr := false + foundScopeAttr := false + + for _, tag := range tags { + if tag.Key == "span.attribute" && tag.Value == "span-value" { + foundSpanAttr = true + } + if tag.Key == "scope.attribute" && tag.Value == "scope-value" { + foundScopeAttr = true + } + } + + assert.True(t, foundSpanAttr, "Span attribute should be present in tags") + assert.True(t, foundScopeAttr, "Scope attribute should be present in tags") +} + type Row map[string]any type BetterFrame struct { frame *data.Frame