add the opentelemetry tracing (#908)
* add the opentelemetry tracing * fix the error sampler config * 添加stream的链路跟踪 * fix the error field namemaster
parent
9672298fa8
commit
5b35fa17de
@ -0,0 +1,60 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
"github.com/tal-tech/go-zero/core/syncx"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/jaeger"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
enabled syncx.AtomicBool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enabled returns if prometheus is enabled.
|
||||||
|
func Enabled() bool {
|
||||||
|
return enabled.True()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAgent starts a prometheus agent.
|
||||||
|
func StartAgent(c Config) {
|
||||||
|
once.Do(func() {
|
||||||
|
if len(c.Endpoint) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Just support jaeger now
|
||||||
|
if c.Batcher != "jaeger" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Endpoint)))
|
||||||
|
if err != nil {
|
||||||
|
logx.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tp := tracesdk.NewTracerProvider(
|
||||||
|
// Set the sampling rate based on the parent span to 100%
|
||||||
|
tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(c.Sampler))),
|
||||||
|
// Always be sure to batch in production.
|
||||||
|
tracesdk.WithBatcher(exp),
|
||||||
|
// Record information about this application in an Resource.
|
||||||
|
tracesdk.WithResource(resource.NewSchemaless(
|
||||||
|
semconv.ServiceNameKey.String(c.Name),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||||
|
|
||||||
|
enabled.Set(true)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
|
grpc_codes "google.golang.org/grpc/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// GRPCStatusCodeKey is convention for numeric status code of a gRPC request.
|
||||||
|
GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code")
|
||||||
|
|
||||||
|
// Name of message transmitted or received.
|
||||||
|
RPCNameKey = attribute.Key("name")
|
||||||
|
|
||||||
|
// Type of message transmitted or received.
|
||||||
|
RPCMessageTypeKey = attribute.Key("message.type")
|
||||||
|
|
||||||
|
// Identifier of message transmitted or received.
|
||||||
|
RPCMessageIDKey = attribute.Key("message.id")
|
||||||
|
|
||||||
|
// The compressed size of the message transmitted or received in bytes.
|
||||||
|
RPCMessageCompressedSizeKey = attribute.Key("message.compressed_size")
|
||||||
|
|
||||||
|
// The uncompressed size of the message transmitted or received in
|
||||||
|
// bytes.
|
||||||
|
RPCMessageUncompressedSizeKey = attribute.Key("message.uncompressed_size")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Semantic conventions for common RPC attributes.
|
||||||
|
var (
|
||||||
|
// Semantic convention for gRPC as the remoting system.
|
||||||
|
RPCSystemGRPC = semconv.RPCSystemKey.String("grpc")
|
||||||
|
|
||||||
|
// Semantic convention for a message named message.
|
||||||
|
RPCNameMessage = RPCNameKey.String("message")
|
||||||
|
|
||||||
|
// Semantic conventions for RPC message types.
|
||||||
|
RPCMessageTypeSent = RPCMessageTypeKey.String("SENT")
|
||||||
|
RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED")
|
||||||
|
)
|
||||||
|
|
||||||
|
func StatusCodeAttr(c grpc_codes.Code) attribute.KeyValue {
|
||||||
|
return GRPCStatusCodeKey.Int64(int64(c))
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type streamEventType int
|
||||||
|
|
||||||
|
type streamEvent struct {
|
||||||
|
Type streamEventType
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
receiveEndEvent streamEventType = iota
|
||||||
|
errorEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientStream struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
|
||||||
|
desc *grpc.StreamDesc
|
||||||
|
events chan streamEvent
|
||||||
|
eventsDone chan struct{}
|
||||||
|
Finished chan error
|
||||||
|
|
||||||
|
receivedMessageID int
|
||||||
|
sentMessageID int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = proto.Marshal
|
||||||
|
|
||||||
|
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++
|
||||||
|
MessageReceived.Event(w.Context(), w.receivedMessageID, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *clientStream) SendMsg(m interface{}) error {
|
||||||
|
err := w.ClientStream.SendMsg(m)
|
||||||
|
|
||||||
|
w.sentMessageID++
|
||||||
|
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 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,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
const (
|
||||||
|
TraceName = "go-zero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Config is a opentelemetry config.
|
||||||
|
type Config struct {
|
||||||
|
Name string `json:",optional"`
|
||||||
|
Endpoint string `json:",optional"`
|
||||||
|
Sampler float64 `json:",default=1.0"`
|
||||||
|
Batcher string `json:",default=jaeger"`
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
MessageSent = messageType(RPCMessageTypeSent)
|
||||||
|
MessageReceived = messageType(RPCMessageTypeReceived)
|
||||||
|
)
|
||||||
|
|
||||||
|
type messageType attribute.KeyValue
|
||||||
|
|
||||||
|
// Event adds an event of the messageType to the span associated with the
|
||||||
|
// passed context with id and size (if message is a proto message).
|
||||||
|
func (m messageType) Event(ctx context.Context, id int, message interface{}) {
|
||||||
|
span := trace.SpanFromContext(ctx)
|
||||||
|
if p, ok := message.(proto.Message); ok {
|
||||||
|
span.AddEvent("message", trace.WithAttributes(
|
||||||
|
attribute.KeyValue(m),
|
||||||
|
RPCMessageIDKey.Int(id),
|
||||||
|
RPCMessageUncompressedSizeKey.Int(proto.Size(p)),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
span.AddEvent("message", trace.WithAttributes(
|
||||||
|
attribute.KeyValue(m),
|
||||||
|
RPCMessageIDKey.Int(id),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// serverStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and
|
||||||
|
// SendMsg method call.
|
||||||
|
type serverStream struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
receivedMessageID int
|
||||||
|
sentMessageID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *serverStream) Context() context.Context {
|
||||||
|
return w.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *serverStream) RecvMsg(m interface{}) error {
|
||||||
|
err := w.ServerStream.RecvMsg(m)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
w.receivedMessageID++
|
||||||
|
MessageReceived.Event(w.Context(), w.receivedMessageID, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *serverStream) SendMsg(m interface{}) error {
|
||||||
|
err := w.ServerStream.SendMsg(m)
|
||||||
|
|
||||||
|
w.sentMessageID++
|
||||||
|
MessageSent.Event(w.Context(), w.sentMessageID, m)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapServerStream(ctx context.Context, ss grpc.ServerStream) *serverStream {
|
||||||
|
return &serverStream{
|
||||||
|
ServerStream: ss,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/baggage"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type metadataSupplier struct {
|
||||||
|
metadata *metadata.MD
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert that metadataSupplier implements the TextMapCarrier interface
|
||||||
|
var _ propagation.TextMapCarrier = &metadataSupplier{}
|
||||||
|
|
||||||
|
func (s *metadataSupplier) Get(key string) string {
|
||||||
|
values := s.metadata.Get(key)
|
||||||
|
if len(values) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return values[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metadataSupplier) Set(key string, value string) {
|
||||||
|
s.metadata.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metadataSupplier) Keys() []string {
|
||||||
|
out := make([]string, 0, len(*s.metadata))
|
||||||
|
for key := range *s.metadata {
|
||||||
|
out = append(out, key)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func Inject(ctx context.Context, p propagation.TextMapPropagator, metadata *metadata.MD) {
|
||||||
|
p.Inject(ctx, &metadataSupplier{
|
||||||
|
metadata: metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Extract(ctx context.Context, p propagation.TextMapPropagator, metadata *metadata.MD) (baggage.Baggage, trace.SpanContext) {
|
||||||
|
ctx = p.Extract(ctx, &metadataSupplier{
|
||||||
|
metadata: metadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
return baggage.FromContext(ctx), trace.SpanContextFromContext(ctx)
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package opentelemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PeerFromCtx(ctx context.Context) string {
|
||||||
|
p, ok := peer.FromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.Addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpanInfo(fullMethod, peerAddress string) (string, []attribute.KeyValue) {
|
||||||
|
attrs := []attribute.KeyValue{RPCSystemGRPC}
|
||||||
|
name, mAttrs := ParseFullMethod(fullMethod)
|
||||||
|
attrs = append(attrs, mAttrs...)
|
||||||
|
attrs = append(attrs, PeerAttr(peerAddress)...)
|
||||||
|
return name, attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
|
||||||
|
name := strings.TrimLeft(fullMethod, "/")
|
||||||
|
parts := strings.SplitN(name, "/", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
// Invalid format, does not follow `/package.service/method`.
|
||||||
|
return name, []attribute.KeyValue(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var attrs []attribute.KeyValue
|
||||||
|
if service := parts[0]; service != "" {
|
||||||
|
attrs = append(attrs, semconv.RPCServiceKey.String(service))
|
||||||
|
}
|
||||||
|
if method := parts[1]; method != "" {
|
||||||
|
attrs = append(attrs, semconv.RPCMethodKey.String(method))
|
||||||
|
}
|
||||||
|
return name, attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func PeerAttr(addr string) []attribute.KeyValue {
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return []attribute.KeyValue(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if host == "" {
|
||||||
|
host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
return []attribute.KeyValue{
|
||||||
|
semconv.NetPeerIPKey.String(host),
|
||||||
|
semconv.NetPeerPortKey.String(port),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package clientinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/core/opentelemetry"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
grpc_codes "google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OpenTracingInterceptor() 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())
|
||||||
|
|
||||||
|
var span trace.Span
|
||||||
|
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)
|
||||||
|
|
||||||
|
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||||
|
|
||||||
|
opentelemetry.MessageReceived.Event(ctx, 1, reply)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s, _ := status.FromError(err)
|
||||||
|
span.SetStatus(codes.Error, s.Message())
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(s.Code()))
|
||||||
|
} else {
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(grpc_codes.OK))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("ecoplants")
|
||||||
|
|
||||||
|
name, attr := opentelemetry.SpanInfo(method, cc.Target())
|
||||||
|
var span trace.Span
|
||||||
|
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() {
|
||||||
|
err := <-stream.Finished
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s, _ := status.FromError(err)
|
||||||
|
span.SetStatus(codes.Error, s.Message())
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(s.Code()))
|
||||||
|
} else {
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(grpc_codes.OK))
|
||||||
|
}
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return stream, nil
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package clientinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/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 := OpenTracingInterceptor()(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)
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/core/opentelemetry"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/baggage"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
grpc_codes "google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnaryOpenTracingInterceptor() grpc.UnaryServerInterceptor {
|
||||||
|
propagator := otel.GetTextMapPropagator()
|
||||||
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
if !opentelemetry.Enabled() {
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestMetadata, _ := metadata.FromIncomingContext(ctx)
|
||||||
|
metadataCopy := requestMetadata.Copy()
|
||||||
|
|
||||||
|
bags, spanCtx := opentelemetry.Extract(ctx, propagator, &metadataCopy)
|
||||||
|
ctx = baggage.ContextWithBaggage(ctx, bags)
|
||||||
|
|
||||||
|
tr := otel.Tracer(opentelemetry.TraceName)
|
||||||
|
name, attr := opentelemetry.SpanInfo(info.FullMethod, opentelemetry.PeerFromCtx(ctx))
|
||||||
|
|
||||||
|
var span trace.Span
|
||||||
|
ctx, span = tr.Start(
|
||||||
|
trace.ContextWithRemoteSpanContext(ctx, spanCtx),
|
||||||
|
name,
|
||||||
|
trace.WithSpanKind(trace.SpanKindServer),
|
||||||
|
trace.WithAttributes(attr...),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
opentelemetry.MessageReceived.Event(ctx, 1, req)
|
||||||
|
|
||||||
|
resp, err := handler(ctx, req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s, _ := status.FromError(err)
|
||||||
|
span.SetStatus(codes.Error, s.Message())
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(s.Code()))
|
||||||
|
opentelemetry.MessageSent.Event(ctx, 1, s.Proto())
|
||||||
|
} else {
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(grpc_codes.OK))
|
||||||
|
opentelemetry.MessageSent.Event(ctx, 1, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamOpenTracingInterceptor() grpc.StreamServerInterceptor {
|
||||||
|
propagator := otel.GetTextMapPropagator()
|
||||||
|
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
ctx := ss.Context()
|
||||||
|
if !opentelemetry.Enabled() {
|
||||||
|
return handler(srv, opentelemetry.WrapServerStream(ctx, ss))
|
||||||
|
}
|
||||||
|
|
||||||
|
requestMetadata, _ := metadata.FromIncomingContext(ctx)
|
||||||
|
metadataCopy := requestMetadata.Copy()
|
||||||
|
|
||||||
|
bags, spanCtx := opentelemetry.Extract(ctx, propagator, &metadataCopy)
|
||||||
|
ctx = baggage.ContextWithBaggage(ctx, bags)
|
||||||
|
|
||||||
|
tr := otel.Tracer(opentelemetry.TraceName)
|
||||||
|
name, attr := opentelemetry.SpanInfo(info.FullMethod, opentelemetry.PeerFromCtx(ctx))
|
||||||
|
ctx, span := tr.Start(
|
||||||
|
trace.ContextWithRemoteSpanContext(ctx, spanCtx),
|
||||||
|
name,
|
||||||
|
trace.WithSpanKind(trace.SpanKindServer),
|
||||||
|
trace.WithAttributes(attr...),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := handler(srv, opentelemetry.WrapServerStream(ctx, ss))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s, _ := status.FromError(err)
|
||||||
|
span.SetStatus(codes.Error, s.Message())
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(s.Code()))
|
||||||
|
} else {
|
||||||
|
span.SetAttributes(opentelemetry.StatusCodeAttr(grpc_codes.OK))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/opentelemetry"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnaryOpenTracingInterceptor_Disable(t *testing.T) {
|
||||||
|
interceptor := UnaryOpenTracingInterceptor()
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryOpenTracingInterceptor_Enabled(t *testing.T) {
|
||||||
|
opentelemetry.StartAgent(opentelemetry.Config{
|
||||||
|
Name: "go-zero-test",
|
||||||
|
Endpoint: "http://localhost:14268/api/traces",
|
||||||
|
Batcher: "jaeger",
|
||||||
|
Sampler: 1.0,
|
||||||
|
})
|
||||||
|
interceptor := UnaryOpenTracingInterceptor()
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/package.TestService.GetUser",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
Loading…
Reference in New Issue