소스 검색

Billing Telemetry (#4474)

Mauricio Araujo 2 년 전
부모
커밋
b5bb16d5f1

+ 13 - 2
api/server/handlers/billing/create.go

@@ -41,13 +41,18 @@ func (c *CreateBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	clientSecret, err := c.Config().BillingManager.CreatePaymentMethod(proj)
+	clientSecret, err := c.Config().BillingManager.CreatePaymentMethod(ctx, proj)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error creating payment method")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating payment method: %w", err)))
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
+		telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
+	)
+
 	c.WriteResult(w, r, clientSecret)
 }
 
@@ -74,12 +79,18 @@ func (c *SetDefaultBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	err := c.Config().BillingManager.SetDefaultPaymentMethod(paymentMethodID, proj)
+	err := c.Config().BillingManager.SetDefaultPaymentMethod(ctx, paymentMethodID, proj)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error setting default payment method")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error setting default payment method: %w", err)))
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
+		telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
+		telemetry.AttributeKV{Key: "payment-method-id", Value: paymentMethodID},
+	)
+
 	c.WriteResult(w, r, "")
 }

+ 7 - 2
api/server/handlers/billing/customer.go

@@ -44,20 +44,25 @@ func (c *CreateBillingCustomerHandler) ServeHTTP(w http.ResponseWriter, r *http.
 	// There is no easy way to pass environment variables to the frontend,
 	// so for now pass via the backend. This is acceptable because the key is
 	// meant to be public
-	publishableKey := c.Config().BillingManager.GetPublishableKey()
+	publishableKey := c.Config().BillingManager.GetPublishableKey(ctx)
 	if proj.BillingID != "" {
 		c.WriteResult(w, r, publishableKey)
 		return
 	}
 
 	// Create customer in Stripe
-	customerID, err := c.Config().BillingManager.CreateCustomer(request.UserEmail, proj)
+	customerID, err := c.Config().BillingManager.CreateCustomer(ctx, request.UserEmail, proj)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error creating billing customer")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating billing customer: %w", err)))
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
+		telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
+	)
+
 	// Update the project record with the customer ID
 	proj.BillingID = customerID
 	_, err = c.Repo().Project().UpdateProject(proj)

+ 5 - 1
api/server/handlers/billing/delete.go

@@ -39,12 +39,16 @@ func (c *DeleteBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	err := c.Config().BillingManager.DeletePaymentMethod(paymentMethodID)
+	err := c.Config().BillingManager.DeletePaymentMethod(ctx, paymentMethodID)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error deleting payment method")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error deleting payment method: %w", err)))
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "payment-method-id", Value: paymentMethodID},
+	)
+
 	c.WriteResult(w, r, "")
 }

+ 7 - 2
api/server/handlers/billing/list.go

@@ -39,7 +39,7 @@ func (c *ListBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	paymentMethods, err := c.Config().BillingManager.ListPaymentMethod(proj)
+	paymentMethods, err := c.Config().BillingManager.ListPaymentMethod(ctx, proj)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error listing payment method")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error listing payment method: %w", err)))
@@ -65,12 +65,17 @@ func (c *CheckPaymentEnabledHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 
 	proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
 
-	paymentEnabled, err := c.Config().BillingManager.CheckPaymentEnabled(proj)
+	paymentEnabled, err := c.Config().BillingManager.CheckPaymentEnabled(ctx, proj)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error checking if payment enabled")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error checking if payment enabled: %w", err)))
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
+		telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
+	)
+
 	c.WriteResult(w, r, paymentEnabled)
 }

+ 1 - 1
api/server/handlers/project/create.go

@@ -57,7 +57,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	if p.Config().ServerConf.StripeSecretKey != "" && p.Config().ServerConf.StripePublishableKey != "" {
 		// Create billing customer for project and set the billing ID
-		billingID, err := p.Config().BillingManager.CreateCustomer(user.Email, proj)
+		billingID, err := p.Config().BillingManager.CreateCustomer(ctx, user.Email, proj)
 		if err != nil {
 			err = telemetry.Error(ctx, span, err, "error creating billing customer")
 			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 1 - 1
api/server/handlers/project/delete.go

@@ -92,7 +92,7 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	err = p.Config().BillingManager.DeleteCustomer(proj)
+	err = p.Config().BillingManager.DeleteCustomer(ctx, proj)
 	if err != nil {
 		e := "error deleting project in billing provider"
 		err = telemetry.Error(ctx, span, err, e)

+ 18 - 16
internal/billing/billing.go

@@ -1,6 +1,8 @@
 package billing
 
 import (
+	"context"
+
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 )
@@ -9,69 +11,69 @@ import (
 type BillingManager interface {
 	// CreateCustomer registers a project in the billing provider. This is currently a one-to-one
 	// mapping with projects and billing customers, because billing and usage are set per project.
-	CreateCustomer(userEmail string, proj *models.Project) (customerID string, err error)
+	CreateCustomer(ctx context.Context, userEmail string, proj *models.Project) (customerID string, err error)
 
 	// DeleteCustomer will delete the customer from the billing provider
-	DeleteCustomer(proj *models.Project) (err error)
+	DeleteCustomer(ctx context.Context, proj *models.Project) (err error)
 
 	// CheckPaymentEnabled will check if the project has a payment method configured
-	CheckPaymentEnabled(proj *models.Project) (paymentEnabled bool, err error)
+	CheckPaymentEnabled(ctx context.Context, proj *models.Project) (paymentEnabled bool, err error)
 
 	// ListPaymentMethod will return all payment methods for the project
-	ListPaymentMethod(proj *models.Project) (paymentMethods []types.PaymentMethod, err error)
+	ListPaymentMethod(ctx context.Context, proj *models.Project) (paymentMethods []types.PaymentMethod, err error)
 
 	// CreatePaymentMethod will add a new payment method to the project in Stripe
-	CreatePaymentMethod(proj *models.Project) (clientSecret string, err error)
+	CreatePaymentMethod(ctx context.Context, proj *models.Project) (clientSecret string, err error)
 
 	// SetDefaultPaymentMethod will set the payment method as default in the customer invoice settings
-	SetDefaultPaymentMethod(paymentMethodID string, proj *models.Project) (err error)
+	SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, proj *models.Project) (err error)
 
 	// DeletePaymentMethod will remove a payment method for the project in Stripe
-	DeletePaymentMethod(paymentMethodID string) (err error)
+	DeletePaymentMethod(ctx context.Context, paymentMethodID string) (err error)
 
 	// GetPublishableKey returns the key used to render frontend components for the billing manager
-	GetPublishableKey() (key string)
+	GetPublishableKey(ctx context.Context) (key string)
 }
 
 // NoopBillingManager performs no billing operations
 type NoopBillingManager struct{}
 
 // CreateCustomer is a no-op
-func (s *NoopBillingManager) CreateCustomer(userEmail string, proj *models.Project) (customerID string, err error) {
+func (s *NoopBillingManager) CreateCustomer(ctx context.Context, userEmail string, proj *models.Project) (customerID string, err error) {
 	return "", nil
 }
 
 // DeleteCustomer is a no-op
-func (s *NoopBillingManager) DeleteCustomer(proj *models.Project) (err error) {
+func (s *NoopBillingManager) DeleteCustomer(ctx context.Context, proj *models.Project) (err error) {
 	return nil
 }
 
 // CheckPaymentEnabled is a  no-op
-func (s *NoopBillingManager) CheckPaymentEnabled(proj *models.Project) (paymentEnabled bool, err error) {
+func (s *NoopBillingManager) CheckPaymentEnabled(ctx context.Context, proj *models.Project) (paymentEnabled bool, err error) {
 	return false, nil
 }
 
 // ListPaymentMethod is a no-op
-func (s *NoopBillingManager) ListPaymentMethod(proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
+func (s *NoopBillingManager) ListPaymentMethod(ctx context.Context, proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
 	return []types.PaymentMethod{}, nil
 }
 
 // CreatePaymentMethod is a no-op
-func (s *NoopBillingManager) CreatePaymentMethod(proj *models.Project) (clientSecret string, err error) {
+func (s *NoopBillingManager) CreatePaymentMethod(ctx context.Context, proj *models.Project) (clientSecret string, err error) {
 	return "", nil
 }
 
 // SetDefaultPaymentMethod is a no-op
-func (s *NoopBillingManager) SetDefaultPaymentMethod(paymentMethodID string, proj *models.Project) (err error) {
+func (s *NoopBillingManager) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, proj *models.Project) (err error) {
 	return nil
 }
 
 // DeletePaymentMethod is a no-op
-func (s *NoopBillingManager) DeletePaymentMethod(paymentMethodID string) (err error) {
+func (s *NoopBillingManager) DeletePaymentMethod(ctx context.Context, paymentMethodID string) (err error) {
 	return nil
 }
 
 // GetPublishableKey is a no-op
-func (s *NoopBillingManager) GetPublishableKey() (key string) {
+func (s *NoopBillingManager) GetPublishableKey(ctx context.Context) (key string) {
 	return ""
 }

+ 51 - 17
internal/billing/stripe.go

@@ -1,10 +1,12 @@
 package billing
 
 import (
+	"context"
 	"fmt"
 
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/telemetry"
 	"github.com/stripe/stripe-go/v76"
 	"github.com/stripe/stripe-go/v76/customer"
 	"github.com/stripe/stripe-go/v76/paymentmethod"
@@ -19,9 +21,16 @@ type StripeBillingManager struct {
 }
 
 // CreateCustomer will create a customer in Stripe only if the project doesn't have a BillingID
-func (s *StripeBillingManager) CreateCustomer(userEmail string, proj *models.Project) (customerID string, err error) {
+func (s *StripeBillingManager) CreateCustomer(ctx context.Context, userEmail string, proj *models.Project) (customerID string, err error) {
+	ctx, span := telemetry.NewSpan(ctx, "create-stripe-customer")
+	defer span.End()
+
 	stripe.Key = s.StripeSecretKey
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
+	)
+
 	if proj.BillingID == "" {
 		// Create customer if not exists
 		customerName := fmt.Sprintf("project_%s", proj.Name)
@@ -33,7 +42,7 @@ func (s *StripeBillingManager) CreateCustomer(userEmail string, proj *models.Pro
 		// Create in Stripe
 		customer, err := customer.New(params)
 		if err != nil {
-			return "", err
+			return "", telemetry.Error(ctx, span, err, "failed to create Stripe customer")
 		}
 
 		customerID = customer.ID
@@ -43,14 +52,21 @@ func (s *StripeBillingManager) CreateCustomer(userEmail string, proj *models.Pro
 }
 
 // DeleteCustomer will delete the customer from the billing provider
-func (s *StripeBillingManager) DeleteCustomer(proj *models.Project) (err error) {
+func (s *StripeBillingManager) DeleteCustomer(ctx context.Context, proj *models.Project) (err error) {
+	ctx, span := telemetry.NewSpan(ctx, "delete-stripe-customer")
+	defer span.End()
+
 	stripe.Key = s.StripeSecretKey
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
+	)
+
 	if proj.BillingID != "" {
 		params := &stripe.CustomerParams{}
 		_, err := customer.Del(proj.BillingID, params)
 		if err != nil {
-			return err
+			return telemetry.Error(ctx, span, err, "failed to delete Stripe customer")
 		}
 	}
 
@@ -58,7 +74,10 @@ func (s *StripeBillingManager) DeleteCustomer(proj *models.Project) (err error)
 }
 
 // CheckPaymentEnabled will return true if the project has a payment method added, false otherwise
-func (s *StripeBillingManager) CheckPaymentEnabled(proj *models.Project) (paymentEnabled bool, err error) {
+func (s *StripeBillingManager) CheckPaymentEnabled(ctx context.Context, proj *models.Project) (paymentEnabled bool, err error) {
+	_, span := telemetry.NewSpan(ctx, "check-stripe-payment-enabled")
+	defer span.End()
+
 	stripe.Key = s.StripeSecretKey
 
 	params := &stripe.PaymentMethodListParams{
@@ -71,7 +90,10 @@ func (s *StripeBillingManager) CheckPaymentEnabled(proj *models.Project) (paymen
 }
 
 // ListPaymentMethod will return all payment methods for the project
-func (s *StripeBillingManager) ListPaymentMethod(proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
+func (s *StripeBillingManager) ListPaymentMethod(ctx context.Context, proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
+	ctx, span := telemetry.NewSpan(ctx, "list-stripe-payment-method")
+	defer span.End()
+
 	stripe.Key = s.StripeSecretKey
 
 	// Get configured payment methods
@@ -83,7 +105,7 @@ func (s *StripeBillingManager) ListPaymentMethod(proj *models.Project) (paymentM
 
 	defaultPaymentExists, defaultPaymentID, err := s.checkDefaultPaymentMethod(proj.BillingID)
 	if err != nil {
-		return paymentMethods, err
+		return paymentMethods, telemetry.Error(ctx, span, err, "failed to list Stripe payment method")
 	}
 
 	for result.Next() {
@@ -107,9 +129,9 @@ func (s *StripeBillingManager) ListPaymentMethod(proj *models.Project) (paymentM
 	// Set default payment method when project has payment methods enabled but
 	// no default setup
 	if len(paymentMethods) > 0 && !defaultPaymentExists {
-		err = s.SetDefaultPaymentMethod(paymentMethods[len(paymentMethods)-1].ID, proj)
+		err = s.SetDefaultPaymentMethod(ctx, paymentMethods[len(paymentMethods)-1].ID, proj)
 		if err != nil {
-			return paymentMethods, err
+			return paymentMethods, telemetry.Error(ctx, span, err, "failed to list Stripe payment method")
 		}
 	}
 
@@ -117,7 +139,10 @@ func (s *StripeBillingManager) ListPaymentMethod(proj *models.Project) (paymentM
 }
 
 // CreatePaymentMethod will add a new payment method to the project in Stripe
-func (s *StripeBillingManager) CreatePaymentMethod(proj *models.Project) (clientSecret string, err error) {
+func (s *StripeBillingManager) CreatePaymentMethod(ctx context.Context, proj *models.Project) (clientSecret string, err error) {
+	ctx, span := telemetry.NewSpan(ctx, "create-stripe-payment-method")
+	defer span.End()
+
 	stripe.Key = s.StripeSecretKey
 
 	params := &stripe.SetupIntentParams{
@@ -130,14 +155,17 @@ func (s *StripeBillingManager) CreatePaymentMethod(proj *models.Project) (client
 
 	intent, err := setupintent.New(params)
 	if err != nil {
-		return "", err
+		return "", telemetry.Error(ctx, span, err, "failed to create Stripe payment method")
 	}
 
 	return intent.ClientSecret, nil
 }
 
 // SetDefaultPaymentMethod will add a new payment method to the project in Stripe
-func (s *StripeBillingManager) SetDefaultPaymentMethod(paymentMethodID string, proj *models.Project) (err error) {
+func (s *StripeBillingManager) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, proj *models.Project) (err error) {
+	ctx, span := telemetry.NewSpan(ctx, "set-default-stripe-payment-method")
+	defer span.End()
+
 	stripe.Key = s.StripeSecretKey
 
 	params := &stripe.CustomerParams{
@@ -148,26 +176,32 @@ func (s *StripeBillingManager) SetDefaultPaymentMethod(paymentMethodID string, p
 
 	_, err = customer.Update(proj.BillingID, params)
 	if err != nil {
-		return err
+		return telemetry.Error(ctx, span, err, "failed to set default Stripe payment method")
 	}
 
 	return nil
 }
 
 // DeletePaymentMethod will remove a payment method for the project in Stripe
-func (s *StripeBillingManager) DeletePaymentMethod(paymentMethodID string) (err error) {
+func (s *StripeBillingManager) DeletePaymentMethod(ctx context.Context, paymentMethodID string) (err error) {
+	ctx, span := telemetry.NewSpan(ctx, "delete-stripe-payment-method")
+	defer span.End()
+
 	stripe.Key = s.StripeSecretKey
 
 	_, err = paymentmethod.Detach(paymentMethodID, nil)
 	if err != nil {
-		return err
+		return telemetry.Error(ctx, span, err, "failed to delete Stripe payment method")
 	}
 
 	return nil
 }
 
-// GetPublishableKey is a no-op
-func (s *StripeBillingManager) GetPublishableKey() (key string) {
+// GetPublishableKey returns the Stripe publishable key
+func (s *StripeBillingManager) GetPublishableKey(ctx context.Context) (key string) {
+	_, span := telemetry.NewSpan(ctx, "get-stripe-publishable-key")
+	defer span.End()
+
 	return s.StripePublishableKey
 }