frontend: use pkg/errors, show full stacktrace
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
307404714b
commit
84ff69147e
6 changed files with 56 additions and 66 deletions
|
@ -20,6 +20,10 @@ spec:
|
|||
value: "7070"
|
||||
- name: LISTEN_ADDR
|
||||
value: "0.0.0.0"
|
||||
- name: GRPC_TRACE
|
||||
value: "all"
|
||||
- name: GRPC_VERBOSITY
|
||||
value: "debug"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
|
|
|
@ -3,7 +3,7 @@ RUN apt update && apt install net-tools telnet
|
|||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN dotnet restore && \
|
||||
dotnet build \
|
||||
dotnet build && \
|
||||
dotnet publish
|
||||
WORKDIR /app/bin/Debug/netcoreapp2.0
|
||||
ENTRYPOINT ["dotnet", "cartservice.dll", "start"]
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
pb "frontend/genproto"
|
||||
)
|
||||
|
@ -36,7 +37,7 @@ func ensureSessionID(next http.HandlerFunc) http.HandlerFunc {
|
|||
MaxAge: cookieMaxAge,
|
||||
})
|
||||
} else if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("unrecognized cookie error: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "unrecognized cookie error"), http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
sessionID = c.Value
|
||||
|
@ -51,17 +52,17 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
|
|||
log.Printf("[home] session_id=%s currency=%s", sessionID(r), currentCurrency(r))
|
||||
currencies, err := fe.getCurrencies(r.Context())
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve currencies: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
products, err := fe.getProducts(r.Context())
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve products: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve products"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cart, err := fe.getCart(r.Context(), sessionID(r))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve cart: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -73,7 +74,7 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
|
|||
for i, p := range products {
|
||||
price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to do currency conversion for product %s: %+v", p.GetId(), err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrapf(err, "failed to do currency conversion for product %s", p.GetId()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ps[i] = productView{p, price}
|
||||
|
@ -93,36 +94,36 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
|
|||
func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
if id == "" {
|
||||
renderHTTPError(w, fmt.Errorf("product id not specified"), http.StatusBadRequest)
|
||||
renderHTTPError(w, errors.New("product id not specified"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log.Printf("[productHandler] id=%s currency=%s session=%s", id, currentCurrency(r), sessionID(r))
|
||||
p, err := fe.getProduct(r.Context(), id)
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve product: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
currencies, err := fe.getCurrencies(r.Context())
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve currencies: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cart, err := fe.getCart(r.Context(), sessionID(r))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve cart: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to convert currency: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "failed to convert currency"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), []string{id})
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to get product recommendations: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "failed to get product recommendations"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -147,19 +148,19 @@ func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Reques
|
|||
quantity, _ := strconv.ParseUint(r.FormValue("quantity"), 10, 32)
|
||||
productID := r.FormValue("product_id")
|
||||
if productID == "" || quantity == 0 {
|
||||
renderHTTPError(w, fmt.Errorf("invalid form input"), http.StatusBadRequest)
|
||||
renderHTTPError(w, errors.New("invalid form input"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log.Printf("[addToCart] product_id=%s qty=%d session_id=%s", productID, quantity, sessionID(r))
|
||||
|
||||
p, err := fe.getProduct(r.Context(), productID)
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve product: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := fe.insertCart(r.Context(), sessionID(r), p.GetId(), int32(quantity)); err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to add to cart: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "failed to add to cart"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("location", "/cart")
|
||||
|
@ -170,7 +171,7 @@ func (fe *frontendServer) emptyCartHandler(w http.ResponseWriter, r *http.Reques
|
|||
log.Printf("[emptyCart] session_id=%s", sessionID(r))
|
||||
|
||||
if err := fe.emptyCart(r.Context(), sessionID(r)); err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to empty cart: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "failed to empty cart"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("location", "/")
|
||||
|
@ -181,24 +182,24 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request
|
|||
log.Printf("[viewCart] session_id=%s", sessionID(r))
|
||||
currencies, err := fe.getCurrencies(r.Context())
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve currencies: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cart, err := fe.getCart(r.Context(), sessionID(r))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve cart: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), cartIDs(cart))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to get product recommendations: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "failed to get product recommendations"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
shippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to get shipping quote: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "failed to get shipping quote"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -212,12 +213,12 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request
|
|||
for i, item := range cart {
|
||||
p, err := fe.getProduct(r.Context(), item.GetProductId())
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not retrieve product #%s: %+v", item.GetProductId(), err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrapf(err, "could not retrieve product #%s", item.GetProductId()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("could not convert currency for product #%s: %+v", item.GetProductId(), err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrapf(err, "could not convert currency for product #%s", item.GetProductId()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -280,7 +281,7 @@ func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Reque
|
|||
Country: country},
|
||||
})
|
||||
if err != nil {
|
||||
renderHTTPError(w, fmt.Errorf("failed to complete the order: %+v", err), http.StatusInternalServerError)
|
||||
renderHTTPError(w, errors.Wrap(err, "failed to complete the order"), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("order #%s completed", order.GetOrder().GetOrderId())
|
||||
|
@ -334,10 +335,12 @@ func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
|
||||
func renderHTTPError(w http.ResponseWriter, err error, code int) {
|
||||
log.Printf("[error] %+v (code=%d)", err, code)
|
||||
log.Printf("[error](code=%d) %+v ", code, err)
|
||||
errMsg := fmt.Sprintf("%+v", err)
|
||||
|
||||
w.WriteHeader(code)
|
||||
templates.ExecuteTemplate(w, "error", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"error": errMsg,
|
||||
"status_code": code,
|
||||
"status": http.StatusText(code)})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"google.golang.org/grpc"
|
||||
|
@ -56,6 +59,7 @@ func main() {
|
|||
if os.Getenv("PORT") != "" {
|
||||
srvPort = os.Getenv("PORT")
|
||||
}
|
||||
addr := os.Getenv("LISTEN_ADDR")
|
||||
svc := new(frontendServer)
|
||||
mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR")
|
||||
mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR")
|
||||
|
@ -64,31 +68,12 @@ func main() {
|
|||
mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR")
|
||||
mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR")
|
||||
|
||||
var err error
|
||||
svc.currencySvcConn, err = grpc.DialContext(ctx, svc.currencySvcAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect currency service: %+v", err)
|
||||
}
|
||||
svc.productCatalogSvcConn, err = grpc.DialContext(ctx, svc.productCatalogSvcAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect productcatalog service: %+v", err)
|
||||
}
|
||||
svc.cartSvcConn, err = grpc.DialContext(ctx, svc.cartSvcAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect cart service at %s: %+v", svc.cartSvcAddr, err)
|
||||
}
|
||||
svc.recommendationSvcConn, err = grpc.DialContext(ctx, svc.recommendationSvcAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect recommendation service at %s: %+v", svc.recommendationSvcAddr, err)
|
||||
}
|
||||
svc.shippingSvcConn, err = grpc.DialContext(ctx, svc.shippingSvcAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect shipping service at %s: %+v", svc.shippingSvcAddr, err)
|
||||
}
|
||||
svc.checkoutSvcConn, err = grpc.DialContext(ctx, svc.checkoutSvcAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect checkout service at %s: %+v", svc.checkoutSvcAddr, err)
|
||||
}
|
||||
mustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr)
|
||||
mustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr)
|
||||
mustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr)
|
||||
mustConnGRPC(ctx, &svc.recommendationSvcConn, svc.recommendationSvcAddr)
|
||||
mustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr)
|
||||
mustConnGRPC(ctx, &svc.checkoutSvcConn, svc.checkoutSvcAddr)
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", ensureSessionID(svc.homeHandler)).Methods(http.MethodGet, http.MethodHead)
|
||||
|
@ -100,8 +85,8 @@ func main() {
|
|||
r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet)
|
||||
r.HandleFunc("/cart/checkout", ensureSessionID(svc.placeOrderHandler)).Methods(http.MethodPost)
|
||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
||||
log.Printf("starting server on :" + srvPort)
|
||||
log.Fatal(http.ListenAndServe(":"+srvPort, r))
|
||||
log.Printf("starting server on " + addr + ":" + srvPort)
|
||||
log.Fatal(http.ListenAndServe(addr+":"+srvPort, r))
|
||||
}
|
||||
|
||||
func mustMapEnv(target *string, envKey string) {
|
||||
|
@ -111,3 +96,11 @@ func mustMapEnv(target *string, envKey string) {
|
|||
}
|
||||
*target = v
|
||||
}
|
||||
|
||||
func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {
|
||||
var err error
|
||||
*conn, err = grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithTimeout(time.Second*3))
|
||||
if err != nil {
|
||||
panic(errors.Wrapf(err, "grpc: failed to connect %s", addr))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,10 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
pb "frontend/genproto"
|
||||
|
||||
"google.golang.org/grpc/status"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -44,10 +41,6 @@ func (fe *frontendServer) getProduct(ctx context.Context, id string) (*pb.Produc
|
|||
|
||||
func (fe *frontendServer) getCart(ctx context.Context, userID string) ([]*pb.CartItem, error) {
|
||||
resp, err := pb.NewCartServiceClient(fe.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID})
|
||||
if status.Code(err) == codes.Canceled {
|
||||
// TODO(ahmetb) remove this workaround when cartservice returns ok response to GetCart() with non-existing users
|
||||
return nil, nil
|
||||
}
|
||||
return resp.GetItems(), err
|
||||
}
|
||||
|
||||
|
@ -85,10 +78,7 @@ func (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.Cart
|
|||
return nil, err
|
||||
}
|
||||
localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert currency for shipping cost: %+v", err)
|
||||
}
|
||||
return localized, nil
|
||||
return localized, errors.Wrap(err, "failed to convert currency for shipping cost")
|
||||
}
|
||||
|
||||
func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) {
|
||||
|
@ -101,7 +91,7 @@ func (fe *frontendServer) getRecommendations(ctx context.Context, userID string,
|
|||
for i, v := range resp.GetProductIds() {
|
||||
p, err := fe.getProduct(ctx, v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get recommended product info (#%s): %+v", v, err)
|
||||
return nil, errors.Wrapf(err, "failed to get recommended product info (#%s)", v)
|
||||
}
|
||||
out[i] = p
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<p>Something has failed. Below are some details for debugging.</p>
|
||||
|
||||
<p><strong>HTTP Status:</strong> {{.status_code}} {{.status}}</p>
|
||||
<pre class="border border-danger"
|
||||
<pre class="border border-danger p-3"
|
||||
style="white-space: pre-wrap; word-break: keep-all;">
|
||||
{{- .error -}}
|
||||
</pre>
|
||||
|
|
Loading…
Reference in a new issue