Jaeger: Run traceID query through backend with feature toggle enabled (#103472)

* Jaeger: Run traceID query through backend with feature toggle enabled

* Reorganize

* Update comments and test

* Fix snapshot test

* Only pass time params if enabled in cofig

* Rename applyVariables to applyTemplateVariables

* Rename transformResponse to transformTraceResponse

* Update comment
This commit is contained in:
Ivana Huckova
2025-04-09 14:13:46 +02:00
committed by GitHub
parent 4eddeaec75
commit 59f3ca09ab
9 changed files with 1333 additions and 13 deletions
+77 -7
View File
@@ -1,6 +1,7 @@
package jaeger
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -11,9 +12,10 @@ import (
)
type JaegerClient struct {
logger log.Logger
url string
httpClient *http.Client
logger log.Logger
url string
httpClient *http.Client
traceIdTimeEnabled bool
}
type ServicesResponse struct {
@@ -24,11 +26,12 @@ type ServicesResponse struct {
Total int `json:"total"`
}
func New(url string, hc *http.Client, logger log.Logger) (JaegerClient, error) {
func New(url string, hc *http.Client, logger log.Logger, traceIdTimeEnabled bool) (JaegerClient, error) {
client := JaegerClient{
logger: logger,
url: url,
httpClient: hc,
logger: logger,
url: url,
httpClient: hc,
traceIdTimeEnabled: traceIdTimeEnabled,
}
return client, nil
}
@@ -88,3 +91,70 @@ func (j *JaegerClient) Operations(s string) ([]string, error) {
operations = response.Data
return operations, err
}
func (j *JaegerClient) Trace(ctx context.Context, traceID string, start, end int64) (TraceResponse, error) {
logger := j.logger.FromContext(ctx)
var response TracesResponse
trace := TraceResponse{}
if traceID == "" {
return trace, backend.DownstreamError(fmt.Errorf("traceID is empty"))
}
traceUrl, err := url.JoinPath(j.url, "/api/traces", url.QueryEscape(traceID))
if err != nil {
return trace, backend.DownstreamError(fmt.Errorf("failed to join url: %w", err))
}
// Add time parameters if provided and traceIdTimeEnabled is true
if j.traceIdTimeEnabled {
if start > 0 || end > 0 {
parsedURL, err := url.Parse(traceUrl)
if err != nil {
return trace, backend.DownstreamError(fmt.Errorf("failed to parse url: %w", err))
}
query := parsedURL.Query()
if start > 0 {
query.Set("start", fmt.Sprintf("%d", start))
}
if end > 0 {
query.Set("end", fmt.Sprintf("%d", end))
}
parsedURL.RawQuery = query.Encode()
traceUrl = parsedURL.String()
}
}
res, err := j.httpClient.Get(traceUrl)
if err != nil {
if backend.IsDownstreamHTTPError(err) {
return trace, backend.DownstreamError(err)
}
return trace, err
}
defer func() {
if err = res.Body.Close(); err != nil {
logger.Error("Failed to close response body", "error", err)
}
}()
if res != nil && res.StatusCode/100 != 2 {
err := backend.DownstreamError(fmt.Errorf("request failed: %s", res.Status))
if backend.ErrorSourceFromHTTPStatus(res.StatusCode) == backend.ErrorSourceDownstream {
return trace, backend.DownstreamError(err)
}
return trace, err
}
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
return trace, err
}
// We only support one trace at a time
// this is how it was implemented in the frontend before
trace = response.Data[0]
return trace, err
}
+114 -2
View File
@@ -1,12 +1,14 @@
package jaeger
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/stretchr/testify/assert"
)
@@ -58,7 +60,7 @@ func TestJaegerClient_Services(t *testing.T) {
}))
defer server.Close()
client, err := New(server.URL, server.Client(), log.NewNullLogger())
client, err := New(server.URL, server.Client(), log.NewNullLogger(), false)
assert.NoError(t, err)
services, err := client.Services()
@@ -147,7 +149,7 @@ func TestJaegerClient_Operations(t *testing.T) {
}))
defer server.Close()
client, err := New(server.URL, server.Client(), log.NewNullLogger())
client, err := New(server.URL, server.Client(), log.NewNullLogger(), false)
assert.NoError(t, err)
operations, err := client.Operations(tt.service)
@@ -164,3 +166,113 @@ func TestJaegerClient_Operations(t *testing.T) {
})
}
}
func TestJaegerClient_Trace(t *testing.T) {
tests := []struct {
name string
traceId string
traceIdTimeEnabled bool
start int64
end int64
mockResponse string
mockStatusCode int
mockStatus string
expectedURL string
expectError bool
expectedError error
}{
{
name: "Successful response with time params enabled",
traceId: "abc123",
traceIdTimeEnabled: true,
start: 1000,
end: 2000,
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
mockStatusCode: http.StatusOK,
mockStatus: "OK",
expectedURL: "/api/traces/abc123?end=2000&start=1000",
expectError: false,
expectedError: nil,
},
{
name: "Successful response with time params disabled",
traceId: "abc123",
traceIdTimeEnabled: false,
start: 1000,
end: 2000,
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
mockStatusCode: http.StatusOK,
mockStatus: "OK",
expectedURL: "/api/traces/abc123",
expectError: false,
expectedError: nil,
},
{
name: "Non-200 response",
traceId: "abc123",
traceIdTimeEnabled: true,
start: 1000,
end: 2000,
mockResponse: "",
mockStatusCode: http.StatusInternalServerError,
mockStatus: "Internal Server Error",
expectedURL: "/api/traces/abc123?end=2000&start=1000",
expectError: true,
expectedError: backend.PluginError(errors.New("Internal Server Error")),
},
{
name: "Invalid JSON response",
traceId: "abc123",
traceIdTimeEnabled: true,
start: 1000,
end: 2000,
mockResponse: `{invalid json`,
mockStatusCode: http.StatusOK,
mockStatus: "OK",
expectedURL: "/api/traces/abc123?end=2000&start=1000",
expectError: true,
expectedError: &json.SyntaxError{},
},
{
name: "Empty trace ID",
traceId: "",
traceIdTimeEnabled: true,
start: 1000,
end: 2000,
mockResponse: `{"data":[]}`,
mockStatusCode: http.StatusOK,
mockStatus: "OK",
expectedURL: "",
expectError: true,
expectedError: backend.DownstreamError(errors.New("traceID is empty")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var actualURL string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
actualURL = r.URL.String()
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
client, err := New(server.URL, server.Client(), log.NewNullLogger(), tt.traceIdTimeEnabled)
assert.NoError(t, err)
trace, err := client.Trace(context.Background(), tt.traceId, tt.start, tt.end)
if tt.expectError {
assert.Error(t, err)
if tt.expectedError != nil {
assert.IsType(t, tt.expectedError, err)
}
} else {
assert.NoError(t, err)
assert.NotNil(t, trace)
}
assert.Equal(t, tt.expectedURL, actualURL)
})
}
}
+23 -1
View File
@@ -2,6 +2,7 @@ package jaeger
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -29,6 +30,12 @@ type datasourceInfo struct {
JaegerClient JaegerClient
}
type datasourceJSONData struct {
TraceIdTimeParams struct {
Enabled bool `json:"enabled"`
} `json:"traceIdTimeParams"`
}
func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc {
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
httpClientOptions, err := settings.HTTPClientOptions(ctx)
@@ -45,8 +52,14 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
return nil, backend.DownstreamError(errors.New("error reading settings: url is empty"))
}
var jsonData datasourceJSONData
err = json.Unmarshal(settings.JSONData, &jsonData)
if err != nil {
return nil, fmt.Errorf("error reading settings: %w", err)
}
logger := logger.FromContext(ctx)
jaegerClient, err := New(settings.URL, httpClient, logger)
jaegerClient, err := New(settings.URL, httpClient, logger, jsonData.TraceIdTimeParams.Enabled)
return &datasourceInfo{JaegerClient: jaegerClient}, err
}
}
@@ -91,3 +104,12 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq
handler := httpadapter.New(s.registerResourceRoutes())
return handler.CallResource(ctx, req, sender)
}
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
dsInfo, err := s.getDSInfo(ctx, req.PluginContext)
if err != nil {
return nil, err
}
return queryData(ctx, dsInfo, req)
}
+89
View File
@@ -0,0 +1,89 @@
package jaeger
import (
"context"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDataSourceInstanceSettings_TraceIdTimeEnabled(t *testing.T) {
tests := []struct {
name string
jsonData string
expectedEnabled bool
expectError bool
}{
{
name: "traceIdTimeParams enabled",
jsonData: `{
"traceIdTimeParams": {
"enabled": true
}
}`,
expectedEnabled: true,
expectError: false,
},
{
name: "traceIdTimeParams disabled",
jsonData: `{
"traceIdTimeParams": {
"enabled": false
}
}`,
expectedEnabled: false,
expectError: false,
},
{
name: "traceIdTimeParams not specified",
jsonData: `{}`,
expectedEnabled: false,
expectError: false,
},
{
name: "traceIdTimeParams without enabled",
jsonData: `{"traceIdTimeParams":{}}`,
expectedEnabled: false,
expectError: false,
},
{
name: "Invalid JSON",
jsonData: `{invalid json`,
expectedEnabled: false,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create instance settings
settings := backend.DataSourceInstanceSettings{
JSONData: []byte(tt.jsonData),
URL: "http://localhost:16686",
}
// Create instance factory
factory := newInstanceSettings(httpclient.NewProvider())
instance, err := factory(context.Background(), settings)
if tt.expectError {
assert.Error(t, err)
return
}
require.NoError(t, err)
require.NotNil(t, instance)
// Get the datasource info
dsInfo, ok := instance.(*datasourceInfo)
require.True(t, ok)
require.NotNil(t, dsInfo)
// Verify the client's traceIdTimeEnabled parameter
assert.Equal(t, tt.expectedEnabled, dsInfo.JaegerClient.traceIdTimeEnabled)
})
}
}
+217
View File
@@ -0,0 +1,217 @@
package jaeger
import (
"context"
"encoding/json"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
)
type jaegerQuery struct {
QueryType string `json:"queryType"`
Service string `json:"service"`
Operation string `json:"operation"`
Query string `json:"query"`
Tags string `json:"tags"`
MinDuration string `json:"minDuration"`
MaxDuration string `json:"maxDuration"`
Limit int `json:"limit"`
}
func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
response := backend.NewQueryDataResponse()
for _, q := range req.Queries {
var query jaegerQuery
err := json.Unmarshal(q.JSON, &query)
if err != nil {
err = backend.DownstreamError(fmt.Errorf("error while parsing the query json. %w", err))
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err)
continue
}
// No query type means traceID query
if query.QueryType == "" {
traces, err := dsInfo.JaegerClient.Trace(ctx, query.Query, q.TimeRange.From.UnixMilli(), q.TimeRange.To.UnixMilli())
if err != nil {
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err)
continue
}
frame := transformTraceResponse(traces, q.RefID)
response.Responses[q.RefID] = backend.DataResponse{
Frames: []*data.Frame{frame},
}
}
}
return response, nil
}
// transformTraceResponse converts Jaeger trace data to a Data frame
func transformTraceResponse(trace TraceResponse, refID string) *data.Frame {
frame := data.NewFrame(refID,
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("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{}),
data.NewField("warnings", nil, []json.RawMessage{}),
data.NewField("stackTraces", nil, []json.RawMessage{}),
)
// Set metadata for trace visualization
frame.Meta = &data.FrameMeta{
PreferredVisualization: "trace",
Custom: map[string]interface{}{
"traceFormat": "jaeger",
},
}
// Process each span in the trace
for _, span := range trace.Spans {
// Find parent span ID
var parentSpanID *string
for _, ref := range span.References {
if ref.RefType == "CHILD_OF" {
s := ref.SpanID
parentSpanID = &s
break
}
}
// Get service name and tags
serviceName := ""
serviceTags := json.RawMessage{}
if process, ok := trace.Processes[span.ProcessID]; ok {
serviceName = process.ServiceName
tagsMarshaled, err := json.Marshal(process.Tags)
if err == nil {
serviceTags = json.RawMessage(tagsMarshaled)
}
}
// Convert logs
logs := json.RawMessage{}
logsMarshaled, err := json.Marshal(span.Logs)
if err == nil {
logs = json.RawMessage(logsMarshaled)
}
// Convert references (excluding parent)
references := json.RawMessage{}
filteredRefs := []TraceSpanReference{}
for _, ref := range span.References {
if parentSpanID == nil || ref.SpanID != *parentSpanID {
filteredRefs = append(filteredRefs, ref)
}
}
refsMarshaled, err := json.Marshal(filteredRefs)
if err == nil {
references = json.RawMessage(refsMarshaled)
}
// Convert tags
tags := json.RawMessage{}
tagsMarshaled, err := json.Marshal(span.Tags)
if err == nil {
tags = json.RawMessage(tagsMarshaled)
}
// Convert warnings
warnings := json.RawMessage{}
warningsMarshaled, err := json.Marshal(span.Warnings)
if err == nil {
warnings = json.RawMessage(warningsMarshaled)
}
// Convert stack traces
stackTraces := json.RawMessage{}
stackTracesMarshaled, err := json.Marshal(span.StackTraces)
if err == nil {
stackTraces = json.RawMessage(stackTracesMarshaled)
}
// Add span to frame
frame.AppendRow(
span.TraceID,
span.SpanID,
parentSpanID,
span.OperationName,
serviceName,
serviceTags,
float64(span.StartTime)/1000, // Convert microseconds to milliseconds
float64(span.Duration)/1000, // Convert microseconds to milliseconds
logs,
references,
tags,
warnings,
stackTraces,
)
}
return frame
}
type TraceKeyValuePair struct {
Key string `json:"key"`
Type string `json:"type"`
Value interface{} `json:"value"`
}
type TraceProcess struct {
ServiceName string `json:"serviceName"`
Tags []TraceKeyValuePair `json:"tags"`
}
type TraceSpanReference struct {
RefType string `json:"refType"`
SpanID string `json:"spanID"`
TraceID string `json:"traceID"`
}
type TraceLog struct {
// Millisecond epoch time
Timestamp int64 `json:"timestamp"`
Fields []TraceKeyValuePair `json:"fields"`
Name string `json:"name"`
}
type Span struct {
TraceID string `json:"traceID"`
SpanID string `json:"spanID"`
ProcessID string `json:"processID"`
OperationName string `json:"operationName"`
// Times are in microseconds
StartTime int64 `json:"startTime"`
Duration int64 `json:"duration"`
Logs []TraceLog `json:"logs"`
References []TraceSpanReference `json:"references"`
Tags []TraceKeyValuePair `json:"tags"`
Warnings []string `json:"warnings"`
Flags int `json:"flags"`
StackTraces []string `json:"stackTraces"`
}
type TraceResponse struct {
Processes map[string]TraceProcess `json:"processes"`
TraceID string `json:"traceID"`
Warnings []string `json:"warnings"`
Spans []Span `json:"spans"`
}
type TracesResponse struct {
Data []TraceResponse `json:"data"`
Errors interface{} `json:"errors"` // TODO: Handle errors, but we were not using them in the frontend either
Limit int `json:"limit"`
Offset int `json:"offset"`
Total int `json:"total"`
}
+186
View File
@@ -0,0 +1,186 @@
package jaeger
import (
"testing"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
)
func TestTransformTraceResponse(t *testing.T) {
t.Run("simple_trace", func(t *testing.T) {
trace := TraceResponse{
TraceID: "3fa414edcef6ad90",
Spans: []Span{
{
TraceID: "3fa414edcef6ad90",
SpanID: "3fa414edcef6ad90",
OperationName: "HTTP GET - api_traces_traceid",
StartTime: 1605873894680409,
Duration: 1049141,
Tags: []TraceKeyValuePair{
{Key: "sampler.type", Type: "string", Value: "probabilistic"},
{Key: "sampler.param", Type: "float64", Value: 1},
},
Logs: []TraceLog{},
ProcessID: "p1",
Warnings: nil,
Flags: 0,
},
{
TraceID: "3fa414edcef6ad90",
SpanID: "0f5c1808567e4403",
OperationName: "/tempopb.Querier/FindTraceByID",
References: []TraceSpanReference{
{
RefType: "CHILD_OF",
TraceID: "3fa414edcef6ad90",
SpanID: "3fa414edcef6ad90",
},
},
StartTime: 1605873894680587,
Duration: 1847,
Tags: []TraceKeyValuePair{
{Key: "component", Type: "string", Value: "gRPC"},
{Key: "span.kind", Type: "string", Value: "client"},
},
Logs: []TraceLog{},
ProcessID: "p1",
Warnings: nil,
Flags: 0,
},
},
Processes: map[string]TraceProcess{
"p1": {
ServiceName: "tempo-querier",
Tags: []TraceKeyValuePair{
{Key: "cluster", Type: "string", Value: "ops-tools1"},
{Key: "container", Type: "string", Value: "tempo-query"},
},
},
},
Warnings: nil,
}
frame := transformTraceResponse(trace, "test")
experimental.CheckGoldenJSONFrame(t, "./testdata", "simple_trace.golden", frame, false)
})
t.Run("complex_trace", func(t *testing.T) {
trace := TraceResponse{
TraceID: "3fa414edcef6ad90",
Spans: []Span{
{
TraceID: "3fa414edcef6ad90",
SpanID: "3fa414edcef6ad90",
OperationName: "HTTP GET - api_traces_traceid",
References: []TraceSpanReference{},
StartTime: 1605873894680409,
Duration: 1049141,
Tags: []TraceKeyValuePair{
{Key: "sampler.type", Type: "string", Value: "probabilistic"},
{Key: "sampler.param", Type: "float64", Value: 1},
{Key: "error", Type: "bool", Value: true},
{Key: "http.status_code", Type: "int", Value: 500},
},
Logs: []TraceLog{
{
Timestamp: 1605873894681000,
Fields: []TraceKeyValuePair{
{Key: "event", Type: "string", Value: "error"},
{Key: "message", Type: "string", Value: "Internal server error"},
},
},
},
ProcessID: "p1",
Warnings: []string{"High latency detected", "Error rate above threshold"},
Flags: 0,
},
{
TraceID: "3fa414edcef6ad90",
SpanID: "0f5c1808567e4403",
OperationName: "/tempopb.Querier/FindTraceByID",
References: []TraceSpanReference{
{
RefType: "CHILD_OF",
TraceID: "3fa414edcef6ad90",
SpanID: "3fa414edcef6ad90",
},
},
StartTime: 1605873894680587,
Duration: 1847,
Tags: []TraceKeyValuePair{
{Key: "component", Type: "string", Value: "gRPC"},
{Key: "span.kind", Type: "string", Value: "client"},
{Key: "error", Type: "bool", Value: true},
{Key: "grpc.status_code", Type: "int", Value: 13},
},
Logs: []TraceLog{
{
Timestamp: 1605873894680700,
Fields: []TraceKeyValuePair{
{Key: "event", Type: "string", Value: "error"},
{Key: "message", Type: "string", Value: "gRPC error: INTERNAL"},
},
},
},
ProcessID: "p1",
Warnings: []string{"gRPC call failed", "Retry attempt 3"},
Flags: 0,
},
{
TraceID: "3fa414edcef6ad90",
SpanID: "1a2b3c4d5e6f7g8h",
OperationName: "db.query",
References: []TraceSpanReference{
{
RefType: "CHILD_OF",
TraceID: "3fa414edcef6ad90",
SpanID: "0f5c1808567e4403",
},
},
StartTime: 1605873894680800,
Duration: 500,
Tags: []TraceKeyValuePair{
{Key: "db.type", Type: "string", Value: "postgresql"},
{Key: "db.statement", Type: "string", Value: "SELECT * FROM traces WHERE id = $1"},
{Key: "error", Type: "bool", Value: true},
},
Logs: []TraceLog{
{
Timestamp: 1605873894680850,
Fields: []TraceKeyValuePair{
{Key: "event", Type: "string", Value: "error"},
{Key: "message", Type: "string", Value: "Database connection timeout"},
},
},
},
ProcessID: "p2",
Warnings: []string{"Database connection slow", "Query timeout"},
Flags: 0,
},
},
Processes: map[string]TraceProcess{
"p1": {
ServiceName: "tempo-querier",
Tags: []TraceKeyValuePair{
{Key: "cluster", Type: "string", Value: "ops-tools1"},
{Key: "container", Type: "string", Value: "tempo-query"},
{Key: "version", Type: "string", Value: "1.2.3"},
},
},
"p2": {
ServiceName: "tempo-storage",
Tags: []TraceKeyValuePair{
{Key: "cluster", Type: "string", Value: "ops-tools1"},
{Key: "container", Type: "string", Value: "tempo-storage"},
{Key: "version", Type: "string", Value: "2.0.1"},
},
},
},
Warnings: []string{"Trace contains errors", "Multiple service failures"},
}
frame := transformTraceResponse(trace, "test")
experimental.CheckGoldenJSONFrame(t, "./testdata", "complex_trace.golden", frame, false)
})
}
+375
View File
@@ -0,0 +1,375 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "typeVersion": [
// 0,
// 0
// ],
// "custom": {
// "traceFormat": "jaeger"
// },
// "preferredVisualisationType": "trace"
// }
// Name: test
// Dimensions: 13 Fields by 3 Rows
// +------------------+------------------+--------------------+--------------------------------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+-------------------------+
// | Name: traceID | Name: spanID | Name: parentSpanID | Name: operationName | Name: serviceName | Name: serviceTags | Name: startTime | Name: duration | Name: logs | Name: references | Name: tags | Name: warnings | Name: stackTraces |
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
// | Type: []string | Type: []string | Type: []*string | Type: []string | Type: []string | Type: []json.RawMessage | Type: []float64 | Type: []float64 | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage |
// +------------------+------------------+--------------------+--------------------------------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+-------------------------+
// | 3fa414edcef6ad90 | 3fa414edcef6ad90 | null | HTTP GET - api_traces_traceid | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"},{"key":"version","type":"string","value":"1.2.3"}] | 1.605873894680409e+12 | 1049.141 | [{"timestamp":1605873894681000,"fields":[{"key":"event","type":"string","value":"error"},{"key":"message","type":"string","value":"Internal server error"}],"name":""}] | [] | [{"key":"sampler.type","type":"string","value":"probabilistic"},{"key":"sampler.param","type":"float64","value":1},{"key":"error","type":"bool","value":true},{"key":"http.status_code","type":"int","value":500}] | ["High latency detected","Error rate above threshold"] | null |
// | 3fa414edcef6ad90 | 0f5c1808567e4403 | 3fa414edcef6ad90 | /tempopb.Querier/FindTraceByID | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"},{"key":"version","type":"string","value":"1.2.3"}] | 1.605873894680587e+12 | 1.847 | [{"timestamp":1605873894680700,"fields":[{"key":"event","type":"string","value":"error"},{"key":"message","type":"string","value":"gRPC error: INTERNAL"}],"name":""}] | [] | [{"key":"component","type":"string","value":"gRPC"},{"key":"span.kind","type":"string","value":"client"},{"key":"error","type":"bool","value":true},{"key":"grpc.status_code","type":"int","value":13}] | ["gRPC call failed","Retry attempt 3"] | null |
// | 3fa414edcef6ad90 | 1a2b3c4d5e6f7g8h | 0f5c1808567e4403 | db.query | tempo-storage | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-storage"},{"key":"version","type":"string","value":"2.0.1"}] | 1.6058738946808e+12 | 0.5 | [{"timestamp":1605873894680850,"fields":[{"key":"event","type":"string","value":"error"},{"key":"message","type":"string","value":"Database connection timeout"}],"name":""}] | [] | [{"key":"db.type","type":"string","value":"postgresql"},{"key":"db.statement","type":"string","value":"SELECT * FROM traces WHERE id = $1"},{"key":"error","type":"bool","value":true}] | ["Database connection slow","Query timeout"] | null |
// +------------------+------------------+--------------------+--------------------------------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+-------------------------+
//
//
// 🌟 This was machine generated. Do not edit. 🌟
{
"status": 200,
"frames": [
{
"schema": {
"name": "test",
"meta": {
"typeVersion": [
0,
0
],
"custom": {
"traceFormat": "jaeger"
},
"preferredVisualisationType": "trace"
},
"fields": [
{
"name": "traceID",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "spanID",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "parentSpanID",
"type": "string",
"typeInfo": {
"frame": "string",
"nullable": true
}
},
{
"name": "operationName",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "serviceName",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "serviceTags",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "startTime",
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
{
"name": "duration",
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
{
"name": "logs",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "references",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "tags",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "warnings",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "stackTraces",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
}
]
},
"data": {
"values": [
[
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90"
],
[
"3fa414edcef6ad90",
"0f5c1808567e4403",
"1a2b3c4d5e6f7g8h"
],
[
null,
"3fa414edcef6ad90",
"0f5c1808567e4403"
],
[
"HTTP GET - api_traces_traceid",
"/tempopb.Querier/FindTraceByID",
"db.query"
],
[
"tempo-querier",
"tempo-querier",
"tempo-storage"
],
[
[
{
"key": "cluster",
"type": "string",
"value": "ops-tools1"
},
{
"key": "container",
"type": "string",
"value": "tempo-query"
},
{
"key": "version",
"type": "string",
"value": "1.2.3"
}
],
[
{
"key": "cluster",
"type": "string",
"value": "ops-tools1"
},
{
"key": "container",
"type": "string",
"value": "tempo-query"
},
{
"key": "version",
"type": "string",
"value": "1.2.3"
}
],
[
{
"key": "cluster",
"type": "string",
"value": "ops-tools1"
},
{
"key": "container",
"type": "string",
"value": "tempo-storage"
},
{
"key": "version",
"type": "string",
"value": "2.0.1"
}
]
],
[
1605873894680.409,
1605873894680.587,
1605873894680.8
],
[
1049.141,
1.847,
0.5
],
[
[
{
"timestamp": 1605873894681000,
"fields": [
{
"key": "event",
"type": "string",
"value": "error"
},
{
"key": "message",
"type": "string",
"value": "Internal server error"
}
],
"name": ""
}
],
[
{
"timestamp": 1605873894680700,
"fields": [
{
"key": "event",
"type": "string",
"value": "error"
},
{
"key": "message",
"type": "string",
"value": "gRPC error: INTERNAL"
}
],
"name": ""
}
],
[
{
"timestamp": 1605873894680850,
"fields": [
{
"key": "event",
"type": "string",
"value": "error"
},
{
"key": "message",
"type": "string",
"value": "Database connection timeout"
}
],
"name": ""
}
]
],
[
[],
[],
[]
],
[
[
{
"key": "sampler.type",
"type": "string",
"value": "probabilistic"
},
{
"key": "sampler.param",
"type": "float64",
"value": 1
},
{
"key": "error",
"type": "bool",
"value": true
},
{
"key": "http.status_code",
"type": "int",
"value": 500
}
],
[
{
"key": "component",
"type": "string",
"value": "gRPC"
},
{
"key": "span.kind",
"type": "string",
"value": "client"
},
{
"key": "error",
"type": "bool",
"value": true
},
{
"key": "grpc.status_code",
"type": "int",
"value": 13
}
],
[
{
"key": "db.type",
"type": "string",
"value": "postgresql"
},
{
"key": "db.statement",
"type": "string",
"value": "SELECT * FROM traces WHERE id = $1"
},
{
"key": "error",
"type": "bool",
"value": true
}
]
],
[
[
"High latency detected",
"Error rate above threshold"
],
[
"gRPC call failed",
"Retry attempt 3"
],
[
"Database connection slow",
"Query timeout"
]
],
[
null,
null,
null
]
]
}
}
]
}
+239
View File
@@ -0,0 +1,239 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "typeVersion": [
// 0,
// 0
// ],
// "custom": {
// "traceFormat": "jaeger"
// },
// "preferredVisualisationType": "trace"
// }
// Name: test
// Dimensions: 13 Fields by 2 Rows
// +------------------+------------------+--------------------+--------------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------+-------------------------+---------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+
// | Name: traceID | Name: spanID | Name: parentSpanID | Name: operationName | Name: serviceName | Name: serviceTags | Name: startTime | Name: duration | Name: logs | Name: references | Name: tags | Name: warnings | Name: stackTraces |
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
// | Type: []string | Type: []string | Type: []*string | Type: []string | Type: []string | Type: []json.RawMessage | Type: []float64 | Type: []float64 | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage |
// +------------------+------------------+--------------------+--------------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------+-------------------------+---------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+
// | 3fa414edcef6ad90 | 3fa414edcef6ad90 | null | HTTP GET - api_traces_traceid | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"}] | 1.605873894680409e+12 | 1049.141 | [] | [] | [{"key":"sampler.type","type":"string","value":"probabilistic"},{"key":"sampler.param","type":"float64","value":1}] | null | null |
// | 3fa414edcef6ad90 | 0f5c1808567e4403 | 3fa414edcef6ad90 | /tempopb.Querier/FindTraceByID | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"}] | 1.605873894680587e+12 | 1.847 | [] | [] | [{"key":"component","type":"string","value":"gRPC"},{"key":"span.kind","type":"string","value":"client"}] | null | null |
// +------------------+------------------+--------------------+--------------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------+-------------------------+---------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+
//
//
// 🌟 This was machine generated. Do not edit. 🌟
{
"status": 200,
"frames": [
{
"schema": {
"name": "test",
"meta": {
"typeVersion": [
0,
0
],
"custom": {
"traceFormat": "jaeger"
},
"preferredVisualisationType": "trace"
},
"fields": [
{
"name": "traceID",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "spanID",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "parentSpanID",
"type": "string",
"typeInfo": {
"frame": "string",
"nullable": true
}
},
{
"name": "operationName",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "serviceName",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "serviceTags",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "startTime",
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
{
"name": "duration",
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
{
"name": "logs",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "references",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "tags",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "warnings",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
},
{
"name": "stackTraces",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
}
]
},
"data": {
"values": [
[
"3fa414edcef6ad90",
"3fa414edcef6ad90"
],
[
"3fa414edcef6ad90",
"0f5c1808567e4403"
],
[
null,
"3fa414edcef6ad90"
],
[
"HTTP GET - api_traces_traceid",
"/tempopb.Querier/FindTraceByID"
],
[
"tempo-querier",
"tempo-querier"
],
[
[
{
"key": "cluster",
"type": "string",
"value": "ops-tools1"
},
{
"key": "container",
"type": "string",
"value": "tempo-query"
}
],
[
{
"key": "cluster",
"type": "string",
"value": "ops-tools1"
},
{
"key": "container",
"type": "string",
"value": "tempo-query"
}
]
],
[
1605873894680.409,
1605873894680.587
],
[
1049.141,
1.847
],
[
[],
[]
],
[
[],
[]
],
[
[
{
"key": "sampler.type",
"type": "string",
"value": "probabilistic"
},
{
"key": "sampler.param",
"type": "float64",
"value": 1
}
],
[
{
"key": "component",
"type": "string",
"value": "gRPC"
},
{
"key": "span.kind",
"type": "string",
"value": "client"
}
]
],
[
null,
null
],
[
null,
null
]
]
}
}
]
}
@@ -69,6 +69,16 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
}
query(options: DataQueryRequest<JaegerQuery>): Observable<DataQueryResponse> {
// No query type means that the query is a trace ID query
// If all targets are trace ID queries, we can use the backend querying
const allTargetsTraceIdQuery = options.targets.every((target) => !target.queryType);
// We have not migrated the node graph to the backend
// If the node graph is disabled, we can use the backend migration
const nodeGraphDisabled = !this.nodeGraph?.enabled;
if (config.featureToggles.jaegerBackendMigration && allTargetsTraceIdQuery && nodeGraphDisabled) {
return super.query(options);
}
// At this moment we expect only one target. In case we somehow change the UI to be able to show multiple
// traces at one we need to change this.
const target: JaegerQuery = options.targets[0];
@@ -131,7 +141,7 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
}
}
let jaegerInterpolated = pick(this.applyVariables(target, options.scopedVars), [
let jaegerInterpolated = pick(this.applyTemplateVariables(target, options.scopedVars), [
'service',
'operation',
'tags',
@@ -176,12 +186,12 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
return {
...query,
datasource: this.getRef(),
...this.applyVariables(query, scopedVars),
...this.applyTemplateVariables(query, scopedVars),
};
});
}
applyVariables(query: JaegerQuery, scopedVars: ScopedVars) {
applyTemplateVariables(query: JaegerQuery, scopedVars: ScopedVars) {
let expandedQuery = { ...query };
if (query.tags && this.templateSrv.containsTemplate(query.tags)) {