feat: opentelemetry integration, removed self designed tracing (#1111)

* feat: opentelemetry integration, removed self designed tracing

* feat: support zipkin on opentelemetry integration

* feat: support zipkin on opentelemetry integration, enable it in conf

* style: format code

* fix: support logx without exporter configured

* fix: check return values

* refactor: simplify code

* refactor: simplify opentelemetry integration

* ci: fix staticcheck errors
This commit is contained in:
Kevin Wan
2021-10-03 20:53:50 +08:00
committed by GitHub
parent 6e34b55ba7
commit 10e7922597
53 changed files with 611 additions and 1525 deletions

View File

@@ -1,92 +0,0 @@
package clientinterceptors
import (
"context"
"github.com/tal-tech/go-zero/core/trace/opentelemetry"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
gcodes "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// UnaryOpenTracingInterceptor returns a grpc.UnaryClientInterceptor for opentelemetry.
func UnaryOpenTracingInterceptor() grpc.UnaryClientInterceptor {
propagator := otel.GetTextMapPropagator()
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
if !opentelemetry.Enabled() {
return invoker(ctx, method, req, reply, cc, opts...)
}
requestMetadata, _ := metadata.FromOutgoingContext(ctx)
metadataCopy := requestMetadata.Copy()
tr := otel.Tracer(opentelemetry.TraceName)
name, attr := opentelemetry.SpanInfo(method, cc.Target())
ctx, span := tr.Start(ctx, name, trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(attr...))
defer span.End()
opentelemetry.Inject(ctx, propagator, &metadataCopy)
ctx = metadata.NewOutgoingContext(ctx, metadataCopy)
opentelemetry.MessageSent.Event(ctx, 1, req)
opentelemetry.MessageReceived.Event(ctx, 1, reply)
if err := invoker(ctx, method, req, reply, cc, opts...); err != nil {
s, _ := status.FromError(err)
span.SetStatus(codes.Error, s.Message())
span.SetAttributes(opentelemetry.StatusCodeAttr(s.Code()))
return err
}
span.SetAttributes(opentelemetry.StatusCodeAttr(gcodes.OK))
return nil
}
}
// StreamOpenTracingInterceptor returns a grpc.StreamClientInterceptor for opentelemetry.
func StreamOpenTracingInterceptor() grpc.StreamClientInterceptor {
propagator := otel.GetTextMapPropagator()
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string,
streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
if !opentelemetry.Enabled() {
return streamer(ctx, desc, cc, method, opts...)
}
requestMetadata, _ := metadata.FromOutgoingContext(ctx)
metadataCopy := requestMetadata.Copy()
tr := otel.Tracer(opentelemetry.TraceName)
name, attr := opentelemetry.SpanInfo(method, cc.Target())
ctx, span := tr.Start(ctx, name, trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(attr...))
opentelemetry.Inject(ctx, propagator, &metadataCopy)
ctx = metadata.NewOutgoingContext(ctx, metadataCopy)
s, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
grpcStatus, _ := status.FromError(err)
span.SetStatus(codes.Error, grpcStatus.Message())
span.SetAttributes(opentelemetry.StatusCodeAttr(grpcStatus.Code()))
span.End()
return s, err
}
stream := opentelemetry.WrapClientStream(ctx, s, desc)
go func() {
if err := <-stream.Finished; err != nil {
s, _ := status.FromError(err)
span.SetStatus(codes.Error, s.Message())
span.SetAttributes(opentelemetry.StatusCodeAttr(s.Code()))
} else {
span.SetAttributes(opentelemetry.StatusCodeAttr(gcodes.OK))
}
span.End()
}()
return stream, nil
}
}

View File

@@ -1,27 +0,0 @@
package clientinterceptors
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/trace/opentelemetry"
"google.golang.org/grpc"
)
func TestOpenTracingInterceptor(t *testing.T) {
opentelemetry.StartAgent(opentelemetry.Config{
Name: "go-zero-test",
Endpoint: "http://localhost:14268/api/traces",
Batcher: "jaeger",
Sampler: 1.0,
})
cc := new(grpc.ClientConn)
err := UnaryOpenTracingInterceptor()(context.Background(), "/ListUser", nil, nil, cc,
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
opts ...grpc.CallOption) error {
return nil
})
assert.Nil(t, err)
}

View File

@@ -2,40 +2,206 @@ package clientinterceptors
import (
"context"
"io"
"github.com/tal-tech/go-zero/core/trace"
ztrace "github.com/tal-tech/go-zero/core/trace"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
gcodes "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// UnaryTracingInterceptor is an interceptor that handles tracing.
const (
receiveEndEvent streamEventType = iota
errorEvent
)
// UnaryTracingInterceptor returns a grpc.UnaryClientInterceptor for opentelemetry.
func UnaryTracingInterceptor(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx, span := trace.StartClientSpan(ctx, cc.Target(), method)
defer span.Finish()
ctx, span := startSpan(ctx, method, cc.Target())
defer span.End()
var pairs []string
span.Visit(func(key, val string) bool {
pairs = append(pairs, key, val)
return true
})
ctx = metadata.AppendToOutgoingContext(ctx, pairs...)
ztrace.MessageSent.Event(ctx, 1, req)
ztrace.MessageReceived.Event(ctx, 1, reply)
return invoker(ctx, method, req, reply, cc, opts...)
if err := invoker(ctx, method, req, reply, cc, opts...); err != nil {
s, ok := status.FromError(err)
if ok {
span.SetStatus(codes.Error, s.Message())
span.SetAttributes(ztrace.StatusCodeAttr(s.Code()))
} else {
span.SetStatus(codes.Error, err.Error())
}
return err
}
span.SetAttributes(ztrace.StatusCodeAttr(gcodes.OK))
return nil
}
// StreamTracingInterceptor is an interceptor that handles tracing for stream requests.
// StreamTracingInterceptor returns a grpc.StreamClientInterceptor for opentelemetry.
func StreamTracingInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn,
method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ctx, span := trace.StartClientSpan(ctx, cc.Target(), method)
defer span.Finish()
ctx, span := startSpan(ctx, method, cc.Target())
s, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
st, ok := status.FromError(err)
if ok {
span.SetStatus(codes.Error, st.Message())
span.SetAttributes(ztrace.StatusCodeAttr(st.Code()))
} else {
span.SetStatus(codes.Error, err.Error())
}
span.End()
return s, err
}
var pairs []string
span.Visit(func(key, val string) bool {
pairs = append(pairs, key, val)
return true
})
ctx = metadata.AppendToOutgoingContext(ctx, pairs...)
stream := wrapClientStream(ctx, s, desc)
return streamer(ctx, desc, cc, method, opts...)
go func() {
if err := <-stream.Finished; err != nil {
s, ok := status.FromError(err)
if ok {
span.SetStatus(codes.Error, s.Message())
} else {
span.SetStatus(codes.Error, err.Error())
}
span.SetAttributes(ztrace.StatusCodeAttr(s.Code()))
} else {
span.SetAttributes(ztrace.StatusCodeAttr(gcodes.OK))
}
span.End()
}()
return stream, nil
}
type (
streamEventType int
streamEvent struct {
Type streamEventType
Err error
}
clientStream struct {
grpc.ClientStream
Finished chan error
desc *grpc.StreamDesc
events chan streamEvent
eventsDone chan struct{}
receivedMessageID int
sentMessageID int
}
)
func (w *clientStream) RecvMsg(m interface{}) error {
err := w.ClientStream.RecvMsg(m)
if err == nil && !w.desc.ServerStreams {
w.sendStreamEvent(receiveEndEvent, nil)
} else if err == io.EOF {
w.sendStreamEvent(receiveEndEvent, nil)
} else if err != nil {
w.sendStreamEvent(errorEvent, err)
} else {
w.receivedMessageID++
ztrace.MessageReceived.Event(w.Context(), w.receivedMessageID, m)
}
return err
}
func (w *clientStream) SendMsg(m interface{}) error {
err := w.ClientStream.SendMsg(m)
w.sentMessageID++
ztrace.MessageSent.Event(w.Context(), w.sentMessageID, m)
if err != nil {
w.sendStreamEvent(errorEvent, err)
}
return err
}
func (w *clientStream) Header() (metadata.MD, error) {
md, err := w.ClientStream.Header()
if err != nil {
w.sendStreamEvent(errorEvent, err)
}
return md, err
}
func (w *clientStream) CloseSend() error {
err := w.ClientStream.CloseSend()
if err != nil {
w.sendStreamEvent(errorEvent, err)
}
return err
}
func (w *clientStream) sendStreamEvent(eventType streamEventType, err error) {
select {
case <-w.eventsDone:
case w.events <- streamEvent{Type: eventType, Err: err}:
}
}
func startSpan(ctx context.Context, method, target string) (context.Context, trace.Span) {
var md metadata.MD
requestMetadata, ok := metadata.FromOutgoingContext(ctx)
if ok {
md = requestMetadata.Copy()
} else {
md = metadata.MD{}
}
tr := otel.Tracer(ztrace.TraceName)
name, attr := ztrace.SpanInfo(method, target)
ctx, span := tr.Start(ctx, name, trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(attr...))
ztrace.Inject(ctx, otel.GetTextMapPropagator(), &md)
ctx = metadata.NewOutgoingContext(ctx, md)
return ctx, span
}
// wrapClientStream wraps s with given ctx and desc.
func wrapClientStream(ctx context.Context, s grpc.ClientStream, desc *grpc.StreamDesc) *clientStream {
events := make(chan streamEvent)
eventsDone := make(chan struct{})
finished := make(chan error)
go func() {
defer close(eventsDone)
for {
select {
case event := <-events:
switch event.Type {
case receiveEndEvent:
finished <- nil
return
case errorEvent:
finished <- event.Err
return
}
case <-ctx.Done():
finished <- ctx.Err()
return
}
}
}()
return &clientStream{
ClientStream: s,
desc: desc,
events: events,
eventsDone: eventsDone,
Finished: finished,
}
}

View File

@@ -9,9 +9,25 @@ import (
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func TestOpenTracingInterceptor(t *testing.T) {
trace.StartAgent(trace.Config{
Name: "go-zero-test",
Endpoint: "http://localhost:14268/api/traces",
Batcher: "jaeger",
Sampler: 1.0,
})
cc := new(grpc.ClientConn)
err := UnaryTracingInterceptor(context.Background(), "/ListUser", nil, nil, cc,
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
opts ...grpc.CallOption) error {
return nil
})
assert.Nil(t, err)
}
func TestUnaryTracingInterceptor(t *testing.T) {
var run int32
var wg sync.WaitGroup
@@ -50,14 +66,8 @@ func TestUnaryTracingInterceptor_GrpcFormat(t *testing.T) {
var run int32
var wg sync.WaitGroup
wg.Add(1)
md := metadata.New(map[string]string{
"foo": "bar",
})
carrier, err := trace.Inject(trace.GrpcFormat, md)
assert.Nil(t, err)
ctx, _ := trace.StartServerSpan(context.Background(), carrier, "user", "/foo")
cc := new(grpc.ClientConn)
err = UnaryTracingInterceptor(ctx, "/foo", nil, nil, cc,
err := UnaryTracingInterceptor(context.Background(), "/foo", nil, nil, cc,
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
opts ...grpc.CallOption) error {
defer wg.Done()
@@ -73,14 +83,8 @@ func TestStreamTracingInterceptor_GrpcFormat(t *testing.T) {
var run int32
var wg sync.WaitGroup
wg.Add(1)
md := metadata.New(map[string]string{
"foo": "bar",
})
carrier, err := trace.Inject(trace.GrpcFormat, md)
assert.Nil(t, err)
ctx, _ := trace.StartServerSpan(context.Background(), carrier, "user", "/foo")
cc := new(grpc.ClientConn)
_, err = StreamTracingInterceptor(ctx, nil, cc, "/foo",
_, err := StreamTracingInterceptor(context.Background(), nil, cc, "/foo",
func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string,
opts ...grpc.CallOption) (grpc.ClientStream, error) {
defer wg.Done()