feat: add trace in httpc (#2011)
This commit is contained in:
@@ -7,13 +7,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
nurl "net/url"
|
nurl "net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/lang"
|
"github.com/zeromicro/go-zero/core/lang"
|
||||||
"github.com/zeromicro/go-zero/core/mapping"
|
"github.com/zeromicro/go-zero/core/mapping"
|
||||||
|
"github.com/zeromicro/go-zero/core/trace"
|
||||||
"github.com/zeromicro/go-zero/rest/httpc/internal"
|
"github.com/zeromicro/go-zero/rest/httpc/internal"
|
||||||
"github.com/zeromicro/go-zero/rest/internal/header"
|
"github.com/zeromicro/go-zero/rest/internal/header"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
|
oteltrace "go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
var interceptors = []internal.Interceptor{
|
var interceptors = []internal.Interceptor{
|
||||||
@@ -150,17 +157,47 @@ func fillPath(u *nurl.URL, val map[string]interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func request(r *http.Request, cli client) (*http.Response, error) {
|
func request(r *http.Request, cli client) (*http.Response, error) {
|
||||||
var respHandlers []internal.ResponseHandler
|
tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
|
||||||
for _, interceptor := range interceptors {
|
propagator := otel.GetTextMapPropagator()
|
||||||
|
|
||||||
|
spanName := r.URL.Path
|
||||||
|
ctx, span := tracer.Start(
|
||||||
|
r.Context(),
|
||||||
|
spanName,
|
||||||
|
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
|
||||||
|
oteltrace.WithAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
respHandlers := make([]internal.ResponseHandler, len(interceptors))
|
||||||
|
for i, interceptor := range interceptors {
|
||||||
var h internal.ResponseHandler
|
var h internal.ResponseHandler
|
||||||
r, h = interceptor(r)
|
r, h = interceptor(r)
|
||||||
respHandlers = append(respHandlers, h)
|
respHandlers[i] = h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientTrace := httptrace.ContextClientTrace(ctx)
|
||||||
|
if clientTrace != nil {
|
||||||
|
ctx = httptrace.WithClientTrace(ctx, clientTrace)
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...)
|
||||||
|
propagator.Inject(ctx, propagation.HeaderCarrier(r.Header))
|
||||||
|
|
||||||
resp, err := cli.do(r)
|
resp, err := cli.do(r)
|
||||||
for i := len(respHandlers) - 1; i >= 0; i-- {
|
for i := len(respHandlers) - 1; i >= 0; i-- {
|
||||||
respHandlers[i](resp, err)
|
respHandlers[i](resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(resp.StatusCode)...)
|
||||||
|
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(resp.StatusCode))
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,25 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/http/httptrace"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
ztrace "github.com/zeromicro/go-zero/core/trace"
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
"github.com/zeromicro/go-zero/rest/internal/header"
|
"github.com/zeromicro/go-zero/rest/internal/header"
|
||||||
"github.com/zeromicro/go-zero/rest/router"
|
"github.com/zeromicro/go-zero/rest/router"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDoRequest(t *testing.T) {
|
func TestDoRequest(t *testing.T) {
|
||||||
|
ztrace.StartAgent(ztrace.Config{
|
||||||
|
Name: "go-zero-test",
|
||||||
|
Endpoint: "http://localhost:14268/api/traces",
|
||||||
|
Batcher: "jaeger",
|
||||||
|
Sampler: 1.0,
|
||||||
|
})
|
||||||
|
|
||||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
}))
|
}))
|
||||||
defer svr.Close()
|
defer svr.Close()
|
||||||
@@ -21,6 +31,8 @@ func TestDoRequest(t *testing.T) {
|
|||||||
resp, err := DoRequest(req)
|
resp, err := DoRequest(req)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
spanContext := trace.SpanContextFromContext(resp.Request.Context())
|
||||||
|
assert.True(t, spanContext.IsValid())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDoRequest_NotFound(t *testing.T) {
|
func TestDoRequest_NotFound(t *testing.T) {
|
||||||
@@ -187,3 +199,17 @@ func TestDo_Json(t *testing.T) {
|
|||||||
_, err = Do(context.Background(), http.MethodPost, svr.URL+"/nodes/:key", data)
|
_, err = Do(context.Background(), http.MethodPost, svr.URL+"/nodes/:key", data)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDo_WithClientHttpTrace(t *testing.T) {
|
||||||
|
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
defer svr.Close()
|
||||||
|
|
||||||
|
_, err := Do(httptrace.WithClientTrace(context.Background(),
|
||||||
|
&httptrace.ClientTrace{
|
||||||
|
DNSStart: func(info httptrace.DNSStartInfo) {
|
||||||
|
assert.Equal(t, "localhost", info.Host)
|
||||||
|
},
|
||||||
|
}), http.MethodGet, svr.URL, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user