|
|
|
package serverinterceptors
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
|
|
|
ztrace "github.com/zeromicro/go-zero/core/trace"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
|
|
"go.opentelemetry.io/otel/baggage"
|
|
|
|
"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 a grpc.UnaryServerInterceptor for opentelemetry.
|
|
|
|
func UnaryTracingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo,
|
|
|
|
handler grpc.UnaryHandler) (any, error) {
|
|
|
|
ctx, span := startSpan(ctx, info.FullMethod)
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
ztrace.MessageReceived.Event(ctx, 1, req)
|
|
|
|
resp, err := handler(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
s, ok := status.FromError(err)
|
|
|
|
if ok {
|
|
|
|
span.SetStatus(codes.Error, s.Message())
|
|
|
|
span.SetAttributes(ztrace.StatusCodeAttr(s.Code()))
|
|
|
|
ztrace.MessageSent.Event(ctx, 1, s.Proto())
|
|
|
|
} else {
|
|
|
|
span.SetStatus(codes.Error, err.Error())
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
span.SetAttributes(ztrace.StatusCodeAttr(gcodes.OK))
|
|
|
|
ztrace.MessageSent.Event(ctx, 1, resp)
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StreamTracingInterceptor returns a grpc.StreamServerInterceptor for opentelemetry.
|
|
|
|
func StreamTracingInterceptor(svr any, ss grpc.ServerStream, info *grpc.StreamServerInfo,
|
|
|
|
handler grpc.StreamHandler) error {
|
|
|
|
ctx, span := startSpan(ss.Context(), info.FullMethod)
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
if err := handler(svr, wrapServerStream(ctx, ss)); 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 any) error {
|
|
|
|
err := w.ServerStream.RecvMsg(m)
|
|
|
|
if err == nil {
|
|
|
|
w.receivedMessageID++
|
|
|
|
ztrace.MessageReceived.Event(w.Context(), w.receivedMessageID, m)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *serverStream) SendMsg(m any) error {
|
|
|
|
err := w.ServerStream.SendMsg(m)
|
|
|
|
w.sentMessageID++
|
|
|
|
ztrace.MessageSent.Event(w.Context(), w.sentMessageID, m)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func startSpan(ctx context.Context, method string) (context.Context, trace.Span) {
|
|
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
md = metadata.MD{}
|
|
|
|
}
|
|
|
|
bags, spanCtx := ztrace.Extract(ctx, otel.GetTextMapPropagator(), &md)
|
|
|
|
ctx = baggage.ContextWithBaggage(ctx, bags)
|
|
|
|
tr := otel.Tracer(ztrace.TraceName)
|
|
|
|
name, attr := ztrace.SpanInfo(method, ztrace.PeerFromCtx(ctx))
|
|
|
|
|
|
|
|
return tr.Start(trace.ContextWithRemoteSpanContext(ctx, spanCtx), name,
|
|
|
|
trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attr...))
|
|
|
|
}
|
|
|
|
|
|
|
|
// wrapServerStream wraps the given grpc.ServerStream with the given context.
|
|
|
|
func wrapServerStream(ctx context.Context, ss grpc.ServerStream) *serverStream {
|
|
|
|
return &serverStream{
|
|
|
|
ServerStream: ss,
|
|
|
|
ctx: ctx,
|
|
|
|
}
|
|
|
|
}
|