github.com/openfga/openfga@v1.5.4-rc1/pkg/middleware/logging/logging.go (about) 1 package logging 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "time" 8 9 "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" 10 "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors" 11 "go.opentelemetry.io/otel/trace" 12 "go.uber.org/zap" 13 "google.golang.org/grpc" 14 "google.golang.org/grpc/metadata" 15 "google.golang.org/grpc/status" 16 "google.golang.org/protobuf/encoding/protojson" 17 "google.golang.org/protobuf/reflect/protoreflect" 18 19 "github.com/openfga/openfga/pkg/logger" 20 "github.com/openfga/openfga/pkg/middleware/requestid" 21 serverErrors "github.com/openfga/openfga/pkg/server/errors" 22 ) 23 24 const ( 25 grpcServiceKey = "grpc_service" 26 grpcMethodKey = "grpc_method" 27 grpcTypeKey = "grpc_type" 28 grpcCodeKey = "grpc_code" 29 requestIDKey = "request_id" 30 traceIDKey = "trace_id" 31 rawRequestKey = "raw_request" 32 rawResponseKey = "raw_response" 33 internalErrorKey = "internal_error" 34 grpcReqCompleteKey = "grpc_req_complete" 35 userAgentKey = "user_agent" 36 37 gatewayUserAgentHeader string = "grpcgateway-user-agent" 38 userAgentHeader string = "user-agent" 39 ) 40 41 // NewLoggingInterceptor creates a new logging interceptor for gRPC unary server requests. 42 func NewLoggingInterceptor(logger logger.Logger) grpc.UnaryServerInterceptor { 43 return interceptors.UnaryServerInterceptor(reportable(logger)) 44 } 45 46 // NewStreamingLoggingInterceptor creates a new streaming logging interceptor for gRPC stream server requests. 47 func NewStreamingLoggingInterceptor(logger logger.Logger) grpc.StreamServerInterceptor { 48 return interceptors.StreamServerInterceptor(reportable(logger)) 49 } 50 51 type reporter struct { 52 ctx context.Context 53 logger logger.Logger 54 fields []zap.Field 55 protomarshaler protojson.MarshalOptions 56 } 57 58 // PostCall is invoked after all PostMsgSend operations. 59 func (r *reporter) PostCall(err error, _ time.Duration) { 60 r.fields = append(r.fields, ctxzap.TagsToFields(r.ctx)...) 61 62 code := serverErrors.ConvertToEncodedErrorCode(status.Convert(err)) 63 r.fields = append(r.fields, zap.Int32(grpcCodeKey, code)) 64 65 if err != nil { 66 var internalError serverErrors.InternalError 67 if errors.As(err, &internalError) { 68 r.fields = append(r.fields, zap.String(internalErrorKey, internalError.Internal().Error())) 69 r.logger.Error(err.Error(), r.fields...) 70 } else { 71 r.fields = append(r.fields, zap.Error(err)) 72 r.logger.Info(grpcReqCompleteKey, r.fields...) 73 } 74 75 return 76 } 77 78 r.logger.Info(grpcReqCompleteKey, r.fields...) 79 } 80 81 // PostMsgSend is invoked once after a unary response or multiple times in 82 // streaming requests after each message has been sent. 83 func (r *reporter) PostMsgSend(msg interface{}, err error, _ time.Duration) { 84 if err != nil { 85 // This is the actual error that customers see. 86 intCode := serverErrors.ConvertToEncodedErrorCode(status.Convert(err)) 87 encodedError := serverErrors.NewEncodedError(intCode, err.Error()) 88 protomsg := encodedError.ActualError 89 if resp, err := json.Marshal(protomsg); err == nil { 90 r.fields = append(r.fields, zap.Any(rawResponseKey, json.RawMessage(resp))) 91 } 92 return 93 } 94 protomsg, ok := msg.(protoreflect.ProtoMessage) 95 if ok { 96 if resp, err := r.protomarshaler.Marshal(protomsg); err == nil { 97 r.fields = append(r.fields, zap.Any(rawResponseKey, json.RawMessage(resp))) 98 } 99 } 100 } 101 102 // PostMsgReceive is invoked after receiving a message in streaming requests. 103 func (r *reporter) PostMsgReceive(msg interface{}, _ error, _ time.Duration) { 104 protomsg, ok := msg.(protoreflect.ProtoMessage) 105 if ok { 106 if req, err := r.protomarshaler.Marshal(protomsg); err == nil { 107 r.fields = append(r.fields, zap.Any(rawRequestKey, json.RawMessage(req))) 108 } 109 } 110 } 111 112 // userAgentFromContext retrieves the user agent field from the provided context. 113 // If the user agent field is not present in the context, the function returns an empty string and false. 114 func userAgentFromContext(ctx context.Context) (string, bool) { 115 if headers, ok := metadata.FromIncomingContext(ctx); ok { 116 if header := headers.Get(gatewayUserAgentHeader); len(header) > 0 { 117 return header[0], true 118 } 119 if header := headers.Get(userAgentHeader); len(header) > 0 { 120 return header[0], true 121 } 122 } 123 return "", false 124 } 125 126 func reportable(l logger.Logger) interceptors.CommonReportableFunc { 127 return func(ctx context.Context, c interceptors.CallMeta) (interceptors.Reporter, context.Context) { 128 fields := []zap.Field{ 129 zap.String(grpcServiceKey, c.Service), 130 zap.String(grpcMethodKey, c.Method), 131 zap.String(grpcTypeKey, string(c.Typ)), 132 } 133 134 spanCtx := trace.SpanContextFromContext(ctx) 135 if spanCtx.HasTraceID() { 136 fields = append(fields, zap.String(traceIDKey, spanCtx.TraceID().String())) 137 } 138 139 if requestID, ok := requestid.FromContext(ctx); ok { 140 fields = append(fields, zap.String(requestIDKey, requestID)) 141 } 142 143 if userAgent, ok := userAgentFromContext(ctx); ok { 144 fields = append(fields, zap.String(userAgentKey, userAgent)) 145 } 146 147 zapLogger := l.(*logger.ZapLogger) 148 149 return &reporter{ 150 ctx: ctxzap.ToContext(ctx, zapLogger.Logger), 151 logger: l, 152 fields: fields, 153 protomarshaler: protojson.MarshalOptions{EmitUnpopulated: true}, 154 }, ctx 155 } 156 }