grafana/pkg/tsdb/zipkin/client.go
Ivana Huckova e5519161f2
Zipkin: Run resource calls through backend with feature toggle enabled (#96139)
* Zipkin: Run resource calls througgh backend with feature toggle enabled

* Update

* Don't return early in createZipkinURL and add tests

* Update pkg/tsdb/zipkin/client.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Fix lint

* Fix tests

---------

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>
2024-11-11 13:04:22 +01:00

169 lines
4.3 KiB
Go

package zipkin
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/openzipkin/zipkin-go/model"
)
type ZipkinClient struct {
logger log.Logger
url string
httpClient *http.Client
}
func New(url string, hc *http.Client, logger log.Logger) (ZipkinClient, error) {
client := ZipkinClient{
logger: logger,
url: url,
httpClient: hc,
}
return client, nil
}
// Services returns list of services
// https://zipkin.io/zipkin-api/#/default/get_services
func (z *ZipkinClient) Services() ([]string, error) {
services := []string{}
u, err := url.JoinPath(z.url, "/api/v2/services")
if err != nil {
return services, backend.DownstreamError(fmt.Errorf("failed to join url: %w", err))
}
res, err := z.httpClient.Get(u)
if err != nil {
return services, err
}
defer func() {
if err = res.Body.Close(); err != nil {
z.logger.Error("Failed to close response body", "error", err)
}
}()
if err := json.NewDecoder(res.Body).Decode(&services); err != nil {
return services, err
}
return services, err
}
// Spans returns list of spans for the given service
// https://zipkin.io/zipkin-api/#/default/get_spans
func (z *ZipkinClient) Spans(serviceName string) ([]string, error) {
spans := []string{}
if serviceName == "" {
return spans, errors.New("invalid/empty serviceName")
}
spansUrl, err := createZipkinURL(z.url, "/api/v2/spans", map[string]string{"serviceName": serviceName})
if err != nil {
return spans, backend.DownstreamError(fmt.Errorf("failed to compose url: %w", err))
}
res, err := z.httpClient.Get(spansUrl)
defer func() {
if res != nil {
if err = res.Body.Close(); err != nil {
z.logger.Error("Failed to close response body", "error", err)
}
}
}()
if err != nil {
return spans, err
}
if err := json.NewDecoder(res.Body).Decode(&spans); err != nil {
return spans, err
}
return spans, err
}
// Traces returns list of traces for the given service and span
// https://zipkin.io/zipkin-api/#/default/get_traces
func (z *ZipkinClient) Traces(serviceName string, spanName string) ([][]model.SpanModel, error) {
traces := [][]model.SpanModel{}
if serviceName == "" {
return traces, errors.New("invalid/empty serviceName")
}
if spanName == "" {
return traces, errors.New("invalid/empty spanName")
}
tracesUrl, err := createZipkinURL(z.url, "/api/v2/traces", map[string]string{"serviceName": serviceName, "spanName": spanName})
if err != nil {
return traces, backend.DownstreamError(fmt.Errorf("failed to compose url: %w", err))
}
res, err := z.httpClient.Get(tracesUrl)
defer func() {
if res != nil {
if err = res.Body.Close(); err != nil {
z.logger.Error("Failed to close response body", "error", err)
}
}
}()
if err != nil {
return traces, err
}
if err := json.NewDecoder(res.Body).Decode(&traces); err != nil {
return traces, err
}
return traces, err
}
// Trace returns trace for the given traceId
// https://zipkin.io/zipkin-api/#/default/get_trace__traceId_
func (z *ZipkinClient) Trace(traceId string) ([]model.SpanModel, error) {
trace := []model.SpanModel{}
if traceId == "" {
return trace, errors.New("invalid/empty traceId")
}
traceUrl, err := url.JoinPath(z.url, "/api/v2/trace", url.QueryEscape(traceId))
if err != nil {
return trace, backend.DownstreamError(fmt.Errorf("failed to join url: %w", err))
}
res, err := z.httpClient.Get(traceUrl)
defer func() {
if res != nil {
if err = res.Body.Close(); err != nil {
z.logger.Error("Failed to close response body", "error", err)
}
}
}()
if err != nil {
return trace, err
}
if err := json.NewDecoder(res.Body).Decode(&trace); err != nil {
return trace, err
}
return trace, err
}
func createZipkinURL(baseURL string, path string, params map[string]string) (string, error) {
// Parse the base URL
finalUrl, err := url.Parse(baseURL)
if err != nil {
return "", err
}
// Add the path
urlPath, err := url.JoinPath(finalUrl.Path, path)
if err != nil {
return "", err
}
finalUrl.Path = urlPath
// If there are query parameters, add them
if len(params) > 0 {
queryParams := finalUrl.Query()
for k, v := range params {
queryParams.Set(k, v)
}
finalUrl.RawQuery = queryParams.Encode()
}
// Return the composed URL as a string
return finalUrl.String(), nil
}