package grpc import ( "context" "log/slog" "time" "gitea.nik4nao.com/nik/home-services/ai-gateway/internal/logger" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/status" ) // LoggingUnaryInterceptor logs one completion record for each unary gRPC call. func LoggingUnaryInterceptor(log *slog.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { method, ok := grpc.Method(ctx) if !ok { method = info.FullMethod } reqLog := requestLogger(ctx, log, method, req) ctx = logger.WithLogger(ctx, reqLog) start := time.Now() resp, err := handler(ctx, req) logCompletion(reqLog, "grpc call completed", status.Code(err), time.Since(start), err) return resp, err } } func requestLogger(ctx context.Context, log *slog.Logger, method string, req any) *slog.Logger { peerAddr := "" if p, ok := peer.FromContext(ctx); ok && p.Addr != nil { peerAddr = p.Addr.String() } source := "" if r, ok := req.(interface{ GetSource() string }); ok { source = r.GetSource() } return log.With("grpc.method", method, "grpc.peer", peerAddr, "source", source) } func logCompletion(log *slog.Logger, msg string, code codes.Code, duration time.Duration, err error) { attrs := []any{ "duration_ms", duration.Milliseconds(), "grpc.code", code.String(), } if err != nil { attrs = append(attrs, "error", err.Error()) } switch code { case codes.OK: log.Info(msg, attrs...) case codes.InvalidArgument, codes.NotFound, codes.Unimplemented: log.Warn(msg, attrs...) default: log.Error(msg, attrs...) } }