mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 01:22:21 +08:00

* Add TracedClient * Handle errors and status codes * Wire up tracing to normal ASH and loki annotation mapping * Add tracing to remote alertmanager * one more spot * and not or * More consistency with other grafana traces, lower cardinality name
126 lines
3.4 KiB
Go
126 lines
3.4 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/grafana/dskit/instrument"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
// Requester executes an HTTP request.
|
|
type Requester interface {
|
|
Do(req *http.Request) (*http.Response, error)
|
|
}
|
|
|
|
// TimedClient instruments a request with metrics. It implements Requester.
|
|
type TimedClient struct {
|
|
client Requester
|
|
collector instrument.Collector
|
|
}
|
|
|
|
type contextKey int
|
|
|
|
// OperationNameContextKey specifies the operation name location within the context
|
|
// for instrumentation.
|
|
const OperationNameContextKey contextKey = 0
|
|
|
|
// NewTimedClient creates a Requester that instruments requests on `client`.
|
|
func NewTimedClient(client Requester, collector instrument.Collector) *TimedClient {
|
|
return &TimedClient{
|
|
client: client,
|
|
collector: collector,
|
|
}
|
|
}
|
|
|
|
// Do executes the request.
|
|
func (c TimedClient) Do(r *http.Request) (*http.Response, error) {
|
|
return TimeRequest(r.Context(), c.operationName(r), c.collector, c.client, r)
|
|
}
|
|
|
|
// RoundTrip implements the RoundTripper interface.
|
|
func (c TimedClient) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
return c.Do(r)
|
|
}
|
|
|
|
func (c TimedClient) operationName(r *http.Request) string {
|
|
operation, _ := r.Context().Value(OperationNameContextKey).(string)
|
|
if operation == "" {
|
|
operation = r.URL.Path
|
|
}
|
|
return operation
|
|
}
|
|
|
|
// TimeRequest performs an HTTP client request and records the duration in a histogram.
|
|
func TimeRequest(ctx context.Context, operation string, coll instrument.Collector, client Requester, request *http.Request) (*http.Response, error) {
|
|
var response *http.Response
|
|
doRequest := func(_ context.Context) error {
|
|
var err error
|
|
response, err = client.Do(request) // nolint:bodyclose
|
|
return err
|
|
}
|
|
toStatusCode := func(err error) string {
|
|
if err == nil {
|
|
return strconv.Itoa(response.StatusCode)
|
|
}
|
|
return "error"
|
|
}
|
|
err := instrument.CollectedRequest(ctx, fmt.Sprintf("%s %s", request.Method, operation),
|
|
coll, toStatusCode, doRequest)
|
|
return response, err
|
|
}
|
|
|
|
// TracedClient instruments a request with tracing. It implements Requester.
|
|
type TracedClient struct {
|
|
client Requester
|
|
tracer tracing.Tracer
|
|
name string
|
|
}
|
|
|
|
func NewTracedClient(client Requester, tracer tracing.Tracer, name string) *TracedClient {
|
|
return &TracedClient{
|
|
client: client,
|
|
tracer: tracer,
|
|
name: name,
|
|
}
|
|
}
|
|
|
|
// Do executes the request.
|
|
func (c TracedClient) Do(r *http.Request) (*http.Response, error) {
|
|
ctx, span := c.tracer.Start(r.Context(), c.name, trace.WithSpanKind(trace.SpanKindClient))
|
|
defer span.End()
|
|
|
|
span.SetAttributes(semconv.HTTPURL(r.URL.String()))
|
|
span.SetAttributes(semconv.HTTPMethod(r.Method))
|
|
|
|
c.tracer.Inject(ctx, r.Header, span)
|
|
|
|
r = r.WithContext(ctx)
|
|
resp, err := c.client.Do(r)
|
|
if err != nil {
|
|
span.SetStatus(codes.Error, "request failed")
|
|
span.RecordError(err)
|
|
} else {
|
|
if resp.ContentLength > 0 {
|
|
span.SetAttributes(attribute.Int64("http.content_length", resp.ContentLength))
|
|
}
|
|
span.SetAttributes(semconv.HTTPStatusCode(resp.StatusCode))
|
|
if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
|
span.RecordError(fmt.Errorf("error with HTTP status code %d", resp.StatusCode))
|
|
}
|
|
}
|
|
|
|
return resp, err
|
|
}
|
|
|
|
// RoundTrip implements the RoundTripper interface.
|
|
func (c TracedClient) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
return c.Do(r)
|
|
}
|