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  }