mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Request trace by id using v2, fallback for v1 when 404 * Show partial traces badge in Trace View * update go work sum * Fix tests * some linting * Fix tests and try to ignore linter * Move no lint * nolint:bodyclose * merge main * Fix null tags in array * Fix test * Update go.sum * Update go.sum
300 lines
8.0 KiB
Go
300 lines
8.0 KiB
Go
package tempo
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
commonv11 "github.com/grafana/tempo/pkg/tempopb/common/v1"
|
|
v1 "github.com/grafana/tempo/pkg/tempopb/resource/v1"
|
|
tracev11 "github.com/grafana/tempo/pkg/tempopb/trace/v1"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
|
)
|
|
|
|
type KeyValue struct {
|
|
Value any `json:"value"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
type TraceLog struct {
|
|
// Millisecond epoch time
|
|
Timestamp float64 `json:"timestamp"`
|
|
Fields []*KeyValue `json:"fields"`
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
type TraceReference struct {
|
|
SpanID string `json:"spanID"`
|
|
TraceID string `json:"traceID"`
|
|
Tags []*KeyValue `json:"tags"`
|
|
}
|
|
|
|
func TraceToFrame(resourceSpans []*tracev11.ResourceSpans) (*data.Frame, error) {
|
|
// In open telemetry format the spans are grouped first by resource/service they originated in and inside that
|
|
// resource they are grouped by the instrumentation library which created them.
|
|
|
|
if len(resourceSpans) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
frame := &data.Frame{
|
|
Name: "Trace",
|
|
Fields: []*data.Field{
|
|
data.NewField("traceID", nil, []string{}),
|
|
data.NewField("spanID", nil, []string{}),
|
|
data.NewField("parentSpanID", nil, []string{}),
|
|
data.NewField("operationName", nil, []string{}),
|
|
data.NewField("serviceName", nil, []string{}),
|
|
data.NewField("kind", nil, []string{}),
|
|
data.NewField("statusCode", nil, []int64{}),
|
|
data.NewField("statusMessage", nil, []string{}),
|
|
data.NewField("instrumentationLibraryName", nil, []string{}),
|
|
data.NewField("instrumentationLibraryVersion", nil, []string{}),
|
|
data.NewField("traceState", nil, []string{}),
|
|
data.NewField("serviceTags", nil, []json.RawMessage{}),
|
|
data.NewField("startTime", nil, []float64{}),
|
|
data.NewField("duration", nil, []float64{}),
|
|
data.NewField("logs", nil, []json.RawMessage{}),
|
|
data.NewField("references", nil, []json.RawMessage{}),
|
|
data.NewField("tags", nil, []json.RawMessage{}),
|
|
},
|
|
Meta: &data.FrameMeta{
|
|
// TODO: use constant once available in the SDK
|
|
PreferredVisualization: "trace",
|
|
},
|
|
}
|
|
|
|
for i := 0; i < len(resourceSpans); i++ {
|
|
rs := resourceSpans[i]
|
|
rows, err := resourceSpansToRows(rs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, row := range rows {
|
|
frame.AppendRow(row...)
|
|
}
|
|
}
|
|
|
|
return frame, nil
|
|
}
|
|
|
|
// resourceSpansToRows processes all the spans for a particular resource/service
|
|
func resourceSpansToRows(rs *tracev11.ResourceSpans) ([][]any, error) {
|
|
resource := rs.Resource
|
|
ilss := rs.ScopeSpans
|
|
|
|
if len(resource.Attributes) == 0 || len(ilss) == 0 {
|
|
return [][]any{}, nil
|
|
}
|
|
|
|
// Approximate the number of the spans as the number of the spans in the first
|
|
// instrumentation library info.
|
|
rows := make([][]any, 0, len(ilss[0].Spans))
|
|
|
|
for i := 0; i < len(ilss); i++ {
|
|
ils := ilss[i]
|
|
|
|
// These are finally the actual spans
|
|
spans := ils.Spans
|
|
|
|
for j := 0; j < len(spans); j++ {
|
|
span := spans[j]
|
|
row, err := spanToSpanRow(span, ils.Scope, resource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if row != nil {
|
|
rows = append(rows, row)
|
|
}
|
|
}
|
|
}
|
|
|
|
return rows, nil
|
|
}
|
|
|
|
func spanToSpanRow(span *tracev11.Span, libraryTags *commonv11.InstrumentationScope, resource *v1.Resource) ([]any, error) {
|
|
// If the id representation changed from hexstring to something else we need to change the transformBase64IDToHexString in the frontend code
|
|
traceID := span.TraceId
|
|
traceIDHex := hex.EncodeToString(traceID[:])
|
|
traceIDHex = strings.TrimPrefix(traceIDHex, strings.Repeat("0", 16))
|
|
|
|
spanID := span.SpanId
|
|
spanIDHex := hex.EncodeToString(spanID[:])
|
|
|
|
parentSpanID := span.ParentSpanId
|
|
parentSpanIDHex := hex.EncodeToString(parentSpanID[:])
|
|
|
|
startTime := float64(span.StartTimeUnixNano) / 1_000_000
|
|
serviceName, serviceTags := resourceToProcess(resource)
|
|
|
|
status := span.Status
|
|
statusCode := int64(status.Code)
|
|
statusMessage := status.Message
|
|
|
|
libraryName := libraryTags.Name
|
|
libraryVersion := libraryTags.Version
|
|
traceState := span.TraceState
|
|
|
|
serviceTagsJson, err := json.Marshal(serviceTags)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal service tags: %w", err)
|
|
}
|
|
|
|
spanTags, err := json.Marshal(getSpanTags(span))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal span tags: %w", err)
|
|
}
|
|
|
|
logs, err := json.Marshal(spanEventsToLogs(span.Events))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal span logs: %w", err)
|
|
}
|
|
|
|
references, err := json.Marshal(spanLinksToReferences(span.Links))
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal span links: %w", err)
|
|
}
|
|
|
|
// Order matters (look at dataframe order)
|
|
return []any{
|
|
traceIDHex,
|
|
spanIDHex,
|
|
parentSpanIDHex,
|
|
span.Name,
|
|
serviceName,
|
|
getSpanKind(span.Kind),
|
|
statusCode,
|
|
statusMessage,
|
|
libraryName,
|
|
libraryVersion,
|
|
traceState,
|
|
json.RawMessage(serviceTagsJson),
|
|
startTime,
|
|
float64(span.EndTimeUnixNano-span.StartTimeUnixNano) / 1_000_000,
|
|
json.RawMessage(logs),
|
|
json.RawMessage(references),
|
|
json.RawMessage(spanTags),
|
|
}, nil
|
|
}
|
|
|
|
func resourceToProcess(resource *v1.Resource) (string, []*KeyValue) {
|
|
attrs := resource.Attributes
|
|
serviceName := ResourceNoServiceName
|
|
if len(attrs) == 0 {
|
|
return serviceName, nil
|
|
}
|
|
|
|
tags := make([]*KeyValue, 0, len(attrs)-1)
|
|
for _, attr := range attrs {
|
|
if attribute.Key(attr.Key) == semconv.ServiceNameKey {
|
|
serviceName = attr.GetValue().GetStringValue()
|
|
}
|
|
tags = append(tags, &KeyValue{Key: attr.Key, Value: getAttributeVal(attr.Value)})
|
|
}
|
|
|
|
return serviceName, tags
|
|
}
|
|
|
|
func getAttributeVal(attr *commonv11.AnyValue) any {
|
|
switch attr.GetValue().(type) {
|
|
case *commonv11.AnyValue_StringValue:
|
|
return attr.GetStringValue()
|
|
case *commonv11.AnyValue_IntValue:
|
|
return attr.GetIntValue()
|
|
case *commonv11.AnyValue_BoolValue:
|
|
return attr.GetBoolValue()
|
|
case *commonv11.AnyValue_DoubleValue:
|
|
return attr.GetDoubleValue()
|
|
case *commonv11.AnyValue_KvlistValue, *commonv11.AnyValue_ArrayValue:
|
|
return attr.GetStringValue()
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func getSpanTags(span *tracev11.Span) []*KeyValue {
|
|
tags := make([]*KeyValue, len(span.Attributes))
|
|
for i, attr := range span.Attributes {
|
|
tags[i] = &KeyValue{Key: attr.Key, Value: getAttributeVal(attr.Value)}
|
|
}
|
|
return tags
|
|
}
|
|
|
|
func getSpanKind(spanKind tracev11.Span_SpanKind) string {
|
|
var tagStr string
|
|
switch spanKind {
|
|
case tracev11.Span_SPAN_KIND_CLIENT:
|
|
tagStr = string(OpenTracingSpanKindClient)
|
|
case tracev11.Span_SPAN_KIND_SERVER:
|
|
tagStr = string(OpenTracingSpanKindServer)
|
|
case tracev11.Span_SPAN_KIND_PRODUCER:
|
|
tagStr = string(OpenTracingSpanKindProducer)
|
|
case tracev11.Span_SPAN_KIND_CONSUMER:
|
|
tagStr = string(OpenTracingSpanKindConsumer)
|
|
case tracev11.Span_SPAN_KIND_INTERNAL:
|
|
tagStr = string(OpenTracingSpanKindInternal)
|
|
default:
|
|
return ""
|
|
}
|
|
|
|
return tagStr
|
|
}
|
|
|
|
func spanEventsToLogs(events []*tracev11.Span_Event) []*TraceLog {
|
|
if len(events) == 0 {
|
|
return nil
|
|
}
|
|
|
|
logs := make([]*TraceLog, 0, len(events))
|
|
for i := 0; i < len(events); i++ {
|
|
event := events[i]
|
|
fields := make([]*KeyValue, 0, len(event.Attributes)+1)
|
|
for _, attr := range event.Attributes {
|
|
fields = append(fields, &KeyValue{Key: attr.Key, Value: getAttributeVal(attr.Value)})
|
|
}
|
|
logs = append(logs, &TraceLog{
|
|
Timestamp: float64(event.TimeUnixNano) / 1_000_000,
|
|
Fields: fields,
|
|
Name: event.Name,
|
|
})
|
|
}
|
|
|
|
return logs
|
|
}
|
|
|
|
func spanLinksToReferences(links []*tracev11.Span_Link) []*TraceReference {
|
|
if len(links) == 0 {
|
|
return nil
|
|
}
|
|
|
|
references := make([]*TraceReference, 0, len(links))
|
|
for i := 0; i < len(links); i++ {
|
|
link := links[i]
|
|
|
|
traceID := link.TraceId
|
|
traceIDHex := hex.EncodeToString(traceID[:])
|
|
traceIDHex = strings.TrimLeft(traceIDHex, "0")
|
|
|
|
spanID := link.SpanId
|
|
spanIDHex := hex.EncodeToString(spanID[:])
|
|
|
|
tags := make([]*KeyValue, 0, len(link.Attributes))
|
|
for _, attr := range link.Attributes {
|
|
tags = append(tags, &KeyValue{Key: attr.Key, Value: getAttributeVal(attr.Value)})
|
|
}
|
|
|
|
references = append(references, &TraceReference{
|
|
TraceID: traceIDHex,
|
|
SpanID: spanIDHex,
|
|
Tags: tags,
|
|
})
|
|
}
|
|
|
|
return references
|
|
}
|