diff --git a/components/payments/cmd/api/internal/api/accounts.go b/components/payments/cmd/api/internal/api/accounts.go index 1b2f7b8b5b..1ccee8b275 100644 --- a/components/payments/cmd/api/internal/api/accounts.go +++ b/components/payments/cmd/api/internal/api/accounts.go @@ -8,11 +8,13 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/backend" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" + "github.com/formancehq/payments/internal/otel" "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/bun/bunpaginate" "github.com/formancehq/stack/libs/go-libs/pointer" "github.com/google/uuid" "github.com/gorilla/mux" + "go.opentelemetry.io/otel/attribute" ) type accountResponse struct { @@ -32,6 +34,9 @@ type accountResponse struct { func listAccountsHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "listAccountsHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") query, err := bunpaginate.Extract[storage.ListAccountsQuery](r, func() (*storage.ListAccountsQuery, error) { @@ -42,12 +47,14 @@ func listAccountsHandler(b backend.Backend) http.HandlerFunc { return pointer.For(storage.NewListAccountsQuery(*options)), nil }) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - cursor, err := b.GetService().ListAccounts(r.Context(), *query) + cursor, err := b.GetService().ListAccounts(ctx, *query) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -101,6 +108,7 @@ func listAccountsHandler(b backend.Backend) http.HandlerFunc { }, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -109,12 +117,18 @@ func listAccountsHandler(b backend.Backend) http.HandlerFunc { func readAccountHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "readAccountHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") accountID := mux.Vars(r)["accountID"] - account, err := b.GetService().GetAccount(r.Context(), accountID) + span.SetAttributes(attribute.String("request.accountID", accountID)) + + account, err := b.GetService().GetAccount(ctx, accountID) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -157,6 +171,7 @@ func readAccountHandler(b backend.Backend) http.HandlerFunc { Data: data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } diff --git a/components/payments/cmd/api/internal/api/balances.go b/components/payments/cmd/api/internal/api/balances.go index 99a74b6e80..064f771bb9 100644 --- a/components/payments/cmd/api/internal/api/balances.go +++ b/components/payments/cmd/api/internal/api/balances.go @@ -10,10 +10,12 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/backend" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" + "github.com/formancehq/payments/internal/otel" "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/bun/bunpaginate" "github.com/formancehq/stack/libs/go-libs/pointer" "github.com/gorilla/mux" + "go.opentelemetry.io/otel/attribute" ) type balancesResponse struct { @@ -27,14 +29,25 @@ type balancesResponse struct { func listBalancesForAccount(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "listBalancesForAccount") + defer span.End() + w.Header().Set("Content-Type", "application/json") balanceQuery, err := populateBalanceQueryFromRequest(r) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } + span.SetAttributes( + attribute.String("request.accountID", balanceQuery.AccountID.String()), + attribute.String("request.currency", balanceQuery.Currency), + attribute.String("request.from", balanceQuery.From.String()), + attribute.String("request.to", balanceQuery.To.String()), + ) + query, err := bunpaginate.Extract[storage.ListBalancesQuery](r, func() (*storage.ListBalancesQuery, error) { options, err := getPagination(r, balanceQuery) if err != nil { @@ -43,6 +56,7 @@ func listBalancesForAccount(b backend.Backend) http.HandlerFunc { return pointer.For(storage.NewListBalancesQuery(*options)), nil }) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } @@ -52,6 +66,7 @@ func listBalancesForAccount(b backend.Backend) http.HandlerFunc { if r.URL.Query().Get("limit") != "" { limit, err := strconv.ParseInt(r.URL.Query().Get("limit"), 10, 64) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } @@ -62,8 +77,9 @@ func listBalancesForAccount(b backend.Backend) http.HandlerFunc { } } - cursor, err := b.GetService().ListBalances(r.Context(), *query) + cursor, err := b.GetService().ListBalances(ctx, *query) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -92,6 +108,7 @@ func listBalancesForAccount(b backend.Backend) http.HandlerFunc { }, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } diff --git a/components/payments/cmd/api/internal/api/balances_test.go b/components/payments/cmd/api/internal/api/balances_test.go index a8fe69c320..cae9cb19a3 100644 --- a/components/payments/cmd/api/internal/api/balances_test.go +++ b/components/payments/cmd/api/internal/api/balances_test.go @@ -14,7 +14,6 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" - "github.com/formancehq/stack/libs/go-libs/api" sharedapi "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/auth" "github.com/formancehq/stack/libs/go-libs/logging" @@ -312,7 +311,7 @@ func TestGetBalances(t *testing.T) { }, } - listBalancesResponse := &api.Cursor[models.Balance]{ + listBalancesResponse := &sharedapi.Cursor[models.Balance]{ PageSize: testCase.pageSize, HasMore: false, Previous: "", diff --git a/components/payments/cmd/api/internal/api/bank_accounts.go b/components/payments/cmd/api/internal/api/bank_accounts.go index 042ba30b98..df1c0a204d 100644 --- a/components/payments/cmd/api/internal/api/bank_accounts.go +++ b/components/payments/cmd/api/internal/api/bank_accounts.go @@ -7,11 +7,13 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/backend" "github.com/formancehq/payments/cmd/api/internal/storage" + "github.com/formancehq/payments/internal/otel" "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/bun/bunpaginate" "github.com/formancehq/stack/libs/go-libs/pointer" "github.com/google/uuid" "github.com/gorilla/mux" + "go.opentelemetry.io/otel/attribute" ) type bankAccountRelatedAccountsResponse struct { @@ -42,6 +44,9 @@ type bankAccountResponse struct { func listBankAccountsHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "listBankAccountsHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") query, err := bunpaginate.Extract[storage.ListBankAccountQuery](r, func() (*storage.ListBankAccountQuery, error) { @@ -52,12 +57,14 @@ func listBankAccountsHandler(b backend.Backend) http.HandlerFunc { return pointer.For(storage.NewListBankAccountQuery(*options)), nil }) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - cursor, err := b.GetService().ListBankAccounts(r.Context(), *query) + cursor, err := b.GetService().ListBankAccounts(ctx, *query) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -102,6 +109,7 @@ func listBankAccountsHandler(b backend.Backend) http.HandlerFunc { }, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -110,21 +118,29 @@ func listBankAccountsHandler(b backend.Backend) http.HandlerFunc { func readBankAccountHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "readBankAccountHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") bankAccountID, err := uuid.Parse(mux.Vars(r)["bankAccountID"]) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrInvalidID, err) return } - account, err := b.GetService().GetBankAccount(r.Context(), bankAccountID, true) + span.SetAttributes(attribute.String("request.bankAccountID", bankAccountID.String())) + + account, err := b.GetService().GetBankAccount(ctx, bankAccountID, true) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } if err := account.Offuscate(); err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -161,6 +177,7 @@ func readBankAccountHandler(b backend.Backend) http.HandlerFunc { Data: data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } diff --git a/components/payments/cmd/api/internal/api/bank_accounts_test.go b/components/payments/cmd/api/internal/api/bank_accounts_test.go index aae32351eb..3396563483 100644 --- a/components/payments/cmd/api/internal/api/bank_accounts_test.go +++ b/components/payments/cmd/api/internal/api/bank_accounts_test.go @@ -12,7 +12,6 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" - "github.com/formancehq/stack/libs/go-libs/api" sharedapi "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/auth" "github.com/formancehq/stack/libs/go-libs/logging" @@ -215,7 +214,7 @@ func TestListBankAccounts(t *testing.T) { }, }, } - listBankAccountsResponse := &api.Cursor[models.BankAccount]{ + listBankAccountsResponse := &sharedapi.Cursor[models.BankAccount]{ PageSize: testCase.pageSize, HasMore: false, Previous: "", diff --git a/components/payments/cmd/api/internal/api/metadata.go b/components/payments/cmd/api/internal/api/metadata.go index d56b307bf3..0040e084c0 100644 --- a/components/payments/cmd/api/internal/api/metadata.go +++ b/components/payments/cmd/api/internal/api/metadata.go @@ -7,34 +7,50 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/backend" "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/internal/models" + "github.com/formancehq/payments/internal/otel" "github.com/formancehq/stack/libs/go-libs/api" "github.com/pkg/errors" + "go.opentelemetry.io/otel/attribute" "github.com/gorilla/mux" ) func updateMetadataHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "updateMetadataHandler") + defer span.End() + paymentID, err := models.PaymentIDFromString(mux.Vars(r)["paymentID"]) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrInvalidID, err) return } + span.SetAttributes(attribute.String("request.paymentID", paymentID.String())) + var metadata service.UpdateMetadataRequest if r.ContentLength == 0 { - api.BadRequest(w, ErrMissingOrInvalidBody, errors.New("body is required")) + var err = errors.New("body is required") + otel.RecordError(span, err) + api.BadRequest(w, ErrMissingOrInvalidBody, err) return } err = json.NewDecoder(r.Body).Decode(&metadata) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrMissingOrInvalidBody, err) return } - err = b.GetService().UpdatePaymentMetadata(r.Context(), *paymentID, metadata) + for k, v := range metadata { + span.SetAttributes(attribute.String("request.metadata."+k, v)) + } + + err = b.GetService().UpdatePaymentMetadata(ctx, *paymentID, metadata) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } diff --git a/components/payments/cmd/api/internal/api/payments.go b/components/payments/cmd/api/internal/api/payments.go index 5c18f727e5..513f2db447 100644 --- a/components/payments/cmd/api/internal/api/payments.go +++ b/components/payments/cmd/api/internal/api/payments.go @@ -10,10 +10,12 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" + "github.com/formancehq/payments/internal/otel" "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/bun/bunpaginate" "github.com/formancehq/stack/libs/go-libs/pointer" "github.com/gorilla/mux" + "go.opentelemetry.io/otel/attribute" ) type paymentResponse struct { @@ -45,22 +47,41 @@ type paymentAdjustment struct { func createPaymentHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "createPaymentHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") var req service.CreatePaymentRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrMissingOrInvalidBody, err) return } + span.SetAttributes( + attribute.String("request.reference", req.Reference), + attribute.String("request.sourceAccountID", req.SourceAccountID), + attribute.String("request.destinationAccountID", req.DestinationAccountID), + attribute.String("request.type", req.Type), + attribute.String("request.connectorID", req.ConnectorID), + attribute.String("request.scheme", req.Scheme), + attribute.String("request.status", req.Status), + attribute.String("request.asset", req.Asset), + attribute.String("request.amount", req.Amount.String()), + attribute.String("request.createdAt", req.CreatedAt.String()), + ) + if err := req.Validate(); err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - payment, err := b.GetService().CreatePayment(r.Context(), &req) + payment, err := b.GetService().CreatePayment(ctx, &req) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -93,6 +114,7 @@ func createPaymentHandler(b backend.Backend) http.HandlerFunc { Data: &data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -101,6 +123,9 @@ func createPaymentHandler(b backend.Backend) http.HandlerFunc { func listPaymentsHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "listPaymentsHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") query, err := bunpaginate.Extract[storage.ListPaymentsQuery](r, func() (*storage.ListPaymentsQuery, error) { @@ -111,12 +136,14 @@ func listPaymentsHandler(b backend.Backend) http.HandlerFunc { return pointer.For(storage.NewListPaymentsQuery(*options)), nil }) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - cursor, err := b.GetService().ListPayments(r.Context(), *query) + cursor, err := b.GetService().ListPayments(ctx, *query) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -182,6 +209,7 @@ func listPaymentsHandler(b backend.Backend) http.HandlerFunc { }, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -190,12 +218,18 @@ func listPaymentsHandler(b backend.Backend) http.HandlerFunc { func readPaymentHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "readPaymentHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") paymentID := mux.Vars(r)["paymentID"] - payment, err := b.GetService().GetPayment(r.Context(), paymentID) + span.SetAttributes(attribute.String("request.paymentID", paymentID)) + + payment, err := b.GetService().GetPayment(ctx, paymentID) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -249,6 +283,7 @@ func readPaymentHandler(b backend.Backend) http.HandlerFunc { Data: &data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } diff --git a/components/payments/cmd/api/internal/api/payments_test.go b/components/payments/cmd/api/internal/api/payments_test.go index 35e71da8e8..9933c2c3ff 100644 --- a/components/payments/cmd/api/internal/api/payments_test.go +++ b/components/payments/cmd/api/internal/api/payments_test.go @@ -15,7 +15,6 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" - "github.com/formancehq/stack/libs/go-libs/api" sharedapi "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/auth" "github.com/formancehq/stack/libs/go-libs/logging" @@ -675,7 +674,7 @@ func TestPayments(t *testing.T) { }, }, } - listPaymentsResponse := &api.Cursor[models.Payment]{ + listPaymentsResponse := &sharedapi.Cursor[models.Payment]{ PageSize: testCase.pageSize, HasMore: false, Previous: "", diff --git a/components/payments/cmd/api/internal/api/pools.go b/components/payments/cmd/api/internal/api/pools.go index dde6dc85aa..12df3448c8 100644 --- a/components/payments/cmd/api/internal/api/pools.go +++ b/components/payments/cmd/api/internal/api/pools.go @@ -4,15 +4,18 @@ import ( "encoding/json" "math/big" "net/http" + "strings" "github.com/formancehq/payments/cmd/api/internal/api/backend" "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/cmd/api/internal/storage" + "github.com/formancehq/payments/internal/otel" "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/bun/bunpaginate" "github.com/formancehq/stack/libs/go-libs/pointer" "github.com/gorilla/mux" "github.com/pkg/errors" + "go.opentelemetry.io/otel/attribute" ) type poolResponse struct { @@ -23,22 +26,33 @@ type poolResponse struct { func createPoolHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "createPoolHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") var createPoolRequest service.CreatePoolRequest err := json.NewDecoder(r.Body).Decode(&createPoolRequest) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrMissingOrInvalidBody, err) return } + span.SetAttributes( + attribute.String("request.name", createPoolRequest.Name), + attribute.String("request.accounts", strings.Join(createPoolRequest.AccountIDs, ",")), + ) + if err := createPoolRequest.Validate(); err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - pool, err := b.GetService().CreatePool(r.Context(), &createPoolRequest) + pool, err := b.GetService().CreatePool(ctx, &createPoolRequest) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -58,6 +72,7 @@ func createPoolHandler(b backend.Backend) http.HandlerFunc { Data: data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -66,26 +81,40 @@ func createPoolHandler(b backend.Backend) http.HandlerFunc { func addAccountToPoolHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "addAccountToPoolHandler") + defer span.End() + poolID, ok := mux.Vars(r)["poolID"] if !ok { - api.BadRequest(w, ErrInvalidID, nil) + var err = errors.New("missing poolID") + otel.RecordError(span, err) + api.BadRequest(w, ErrInvalidID, err) return } + span.SetAttributes(attribute.String("request.poolID", poolID)) + var addAccountToPoolRequest service.AddAccountToPoolRequest err := json.NewDecoder(r.Body).Decode(&addAccountToPoolRequest) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrMissingOrInvalidBody, err) return } + span.SetAttributes( + attribute.String("request.accountID", addAccountToPoolRequest.AccountID), + ) + if err := addAccountToPoolRequest.Validate(); err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - err = b.GetService().AddAccountToPool(r.Context(), poolID, &addAccountToPoolRequest) + err = b.GetService().AddAccountToPool(ctx, poolID, &addAccountToPoolRequest) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -96,20 +125,32 @@ func addAccountToPoolHandler(b backend.Backend) http.HandlerFunc { func removeAccountFromPoolHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "removeAccountFromPoolHandler") + defer span.End() + poolID, ok := mux.Vars(r)["poolID"] if !ok { - api.BadRequest(w, ErrInvalidID, nil) + var err = errors.New("missing poolID") + otel.RecordError(span, err) + api.BadRequest(w, ErrInvalidID, err) return } + span.SetAttributes(attribute.String("request.poolID", poolID)) + accountID, ok := mux.Vars(r)["accountID"] if !ok { - api.BadRequest(w, ErrInvalidID, nil) + var err = errors.New("missing accountID") + otel.RecordError(span, err) + api.BadRequest(w, ErrInvalidID, err) return } - err := b.GetService().RemoveAccountFromPool(r.Context(), poolID, accountID) + span.SetAttributes(attribute.String("request.accountID", accountID)) + + err := b.GetService().RemoveAccountFromPool(ctx, poolID, accountID) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -120,6 +161,9 @@ func removeAccountFromPoolHandler(b backend.Backend) http.HandlerFunc { func listPoolHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "listPoolHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") query, err := bunpaginate.Extract[storage.ListPoolsQuery](r, func() (*storage.ListPoolsQuery, error) { @@ -130,12 +174,14 @@ func listPoolHandler(b backend.Backend) http.HandlerFunc { return pointer.For(storage.NewListPoolsQuery(*options)), nil }) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - cursor, err := b.GetService().ListPools(r.Context(), *query) + cursor, err := b.GetService().ListPools(ctx, *query) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -166,6 +212,7 @@ func listPoolHandler(b backend.Backend) http.HandlerFunc { }, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -174,14 +221,22 @@ func listPoolHandler(b backend.Backend) http.HandlerFunc { func getPoolHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "getPoolHandler") + defer span.End() + poolID, ok := mux.Vars(r)["poolID"] if !ok { - api.BadRequest(w, ErrInvalidID, nil) + err := errors.New("missing poolID") + otel.RecordError(span, err) + api.BadRequest(w, ErrInvalidID, err) return } - pool, err := b.GetService().GetPool(r.Context(), poolID) + span.SetAttributes(attribute.String("request.poolID", poolID)) + + pool, err := b.GetService().GetPool(ctx, poolID) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -201,6 +256,7 @@ func getPoolHandler(b backend.Backend) http.HandlerFunc { Data: data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -218,20 +274,32 @@ type poolBalanceResponse struct { func getPoolBalances(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "getPoolBalances") + defer span.End() + poolID, ok := mux.Vars(r)["poolID"] if !ok { - api.BadRequest(w, ErrInvalidID, errors.New("missing poolID")) + var err = errors.New("missing poolID") + otel.RecordError(span, err) + api.BadRequest(w, ErrInvalidID, err) return } + span.SetAttributes(attribute.String("request.poolID", poolID)) + atTime := r.URL.Query().Get("at") if atTime == "" { - api.BadRequest(w, ErrValidation, errors.New("missing atTime")) + var err = errors.New("missing atTime") + otel.RecordError(span, err) + api.BadRequest(w, ErrValidation, err) return } - balance, err := b.GetService().GetPoolBalance(r.Context(), poolID, atTime) + span.SetAttributes(attribute.String("request.atTime", atTime)) + + balance, err := b.GetService().GetPoolBalance(ctx, poolID, atTime) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -251,6 +319,7 @@ func getPoolBalances(b backend.Backend) http.HandlerFunc { Data: data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -259,14 +328,22 @@ func getPoolBalances(b backend.Backend) http.HandlerFunc { func deletePoolHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "deletePoolHandler") + defer span.End() + poolID, ok := mux.Vars(r)["poolID"] if !ok { - api.BadRequest(w, ErrInvalidID, nil) + var err = errors.New("missing poolID") + otel.RecordError(span, err) + api.BadRequest(w, ErrInvalidID, err) return } - err := b.GetService().DeletePool(r.Context(), poolID) + span.SetAttributes(attribute.String("request.poolID", poolID)) + + err := b.GetService().DeletePool(ctx, poolID) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } diff --git a/components/payments/cmd/api/internal/api/pools_test.go b/components/payments/cmd/api/internal/api/pools_test.go index 45c522a3d3..c7a6722245 100644 --- a/components/payments/cmd/api/internal/api/pools_test.go +++ b/components/payments/cmd/api/internal/api/pools_test.go @@ -15,7 +15,6 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" - "github.com/formancehq/stack/libs/go-libs/api" sharedapi "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/auth" "github.com/formancehq/stack/libs/go-libs/logging" @@ -607,7 +606,7 @@ func TestListPools(t *testing.T) { }, }, } - listPoolsResponse := &api.Cursor[models.Pool]{ + listPoolsResponse := &sharedapi.Cursor[models.Pool]{ PageSize: testCase.pageSize, HasMore: false, Previous: "", diff --git a/components/payments/cmd/api/internal/api/transfer_initiation.go b/components/payments/cmd/api/internal/api/transfer_initiation.go index 317b92ff74..b799f748ec 100644 --- a/components/payments/cmd/api/internal/api/transfer_initiation.go +++ b/components/payments/cmd/api/internal/api/transfer_initiation.go @@ -9,10 +9,12 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/backend" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" + "github.com/formancehq/payments/internal/otel" "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/bun/bunpaginate" "github.com/formancehq/stack/libs/go-libs/pointer" "github.com/gorilla/mux" + "go.opentelemetry.io/otel/attribute" ) type transferInitiationResponse struct { @@ -57,16 +59,23 @@ type readTransferInitiationResponse struct { func readTransferInitiationHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "readTransferInitiationHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") transferID, err := models.TransferInitiationIDFromString(mux.Vars(r)["transferID"]) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrInvalidID, err) return } - ret, err := b.GetService().ReadTransferInitiation(r.Context(), transferID) + span.SetAttributes(attribute.String("request.transferID", transferID.String())) + + ret, err := b.GetService().ReadTransferInitiation(ctx, transferID) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -119,6 +128,7 @@ func readTransferInitiationHandler(b backend.Backend) http.HandlerFunc { Data: data, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } @@ -127,6 +137,9 @@ func readTransferInitiationHandler(b backend.Backend) http.HandlerFunc { func listTransferInitiationsHandler(b backend.Backend) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Tracer().Start(r.Context(), "listTransferInitiationsHandler") + defer span.End() + w.Header().Set("Content-Type", "application/json") query, err := bunpaginate.Extract[storage.ListTransferInitiationsQuery](r, func() (*storage.ListTransferInitiationsQuery, error) { @@ -137,12 +150,14 @@ func listTransferInitiationsHandler(b backend.Backend) http.HandlerFunc { return pointer.For(storage.NewListTransferInitiationsQuery(*options)), nil }) if err != nil { + otel.RecordError(span, err) api.BadRequest(w, ErrValidation, err) return } - cursor, err := b.GetService().ListTransferInitiations(r.Context(), *query) + cursor, err := b.GetService().ListTransferInitiations(ctx, *query) if err != nil { + otel.RecordError(span, err) handleServiceErrors(w, r, err) return } @@ -185,6 +200,7 @@ func listTransferInitiationsHandler(b backend.Backend) http.HandlerFunc { }, }) if err != nil { + otel.RecordError(span, err) api.InternalServerError(w, r, err) return } diff --git a/components/payments/cmd/api/internal/api/transfer_initiation_test.go b/components/payments/cmd/api/internal/api/transfer_initiation_test.go index d7a1634447..ff1896a6e1 100644 --- a/components/payments/cmd/api/internal/api/transfer_initiation_test.go +++ b/components/payments/cmd/api/internal/api/transfer_initiation_test.go @@ -13,7 +13,6 @@ import ( "github.com/formancehq/payments/cmd/api/internal/api/service" "github.com/formancehq/payments/cmd/api/internal/storage" "github.com/formancehq/payments/internal/models" - "github.com/formancehq/stack/libs/go-libs/api" sharedapi "github.com/formancehq/stack/libs/go-libs/api" "github.com/formancehq/stack/libs/go-libs/auth" "github.com/formancehq/stack/libs/go-libs/logging" @@ -279,7 +278,7 @@ func TestListTransferInitiations(t *testing.T) { }, }, } - listTFsResponse := &api.Cursor[models.TransferInitiation]{ + listTFsResponse := &sharedapi.Cursor[models.TransferInitiation]{ PageSize: testCase.pageSize, HasMore: false, Previous: "",