frontend: use pkg/errors, show full stacktrace

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan 2018-06-28 22:03:39 -07:00
parent f8ddac9c52
commit ca3ace3f65
6 changed files with 56 additions and 66 deletions

View file

@ -20,6 +20,10 @@ spec:
value: "7070" value: "7070"
- name: LISTEN_ADDR - name: LISTEN_ADDR
value: "0.0.0.0" value: "0.0.0.0"
- name: GRPC_TRACE
value: "all"
- name: GRPC_VERBOSITY
value: "debug"
resources: resources:
requests: requests:
cpu: 200m cpu: 200m

View file

@ -3,7 +3,7 @@ RUN apt update && apt install net-tools telnet
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN dotnet restore && \ RUN dotnet restore && \
dotnet build \ dotnet build && \
dotnet publish dotnet publish
WORKDIR /app/bin/Debug/netcoreapp2.0 WORKDIR /app/bin/Debug/netcoreapp2.0
ENTRYPOINT ["dotnet", "cartservice.dll", "start"] ENTRYPOINT ["dotnet", "cartservice.dll", "start"]

View file

@ -12,6 +12,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors"
pb "frontend/genproto" pb "frontend/genproto"
) )
@ -36,7 +37,7 @@ func ensureSessionID(next http.HandlerFunc) http.HandlerFunc {
MaxAge: cookieMaxAge, MaxAge: cookieMaxAge,
}) })
} else if err != nil { } 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 return
} else { } else {
sessionID = c.Value 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)) log.Printf("[home] session_id=%s currency=%s", sessionID(r), currentCurrency(r))
currencies, err := fe.getCurrencies(r.Context()) currencies, err := fe.getCurrencies(r.Context())
if err != nil { 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 return
} }
products, err := fe.getProducts(r.Context()) products, err := fe.getProducts(r.Context())
if err != nil { 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 return
} }
cart, err := fe.getCart(r.Context(), sessionID(r)) cart, err := fe.getCart(r.Context(), sessionID(r))
if err != nil { 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 return
} }
@ -73,7 +74,7 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
for i, p := range products { for i, p := range products {
price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))
if err != nil { 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 return
} }
ps[i] = productView{p, price} 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) { func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
if id == "" { if id == "" {
renderHTTPError(w, fmt.Errorf("product id not specified"), http.StatusBadRequest) renderHTTPError(w, errors.New("product id not specified"), http.StatusBadRequest)
return return
} }
log.Printf("[productHandler] id=%s currency=%s session=%s", id, currentCurrency(r), sessionID(r)) log.Printf("[productHandler] id=%s currency=%s session=%s", id, currentCurrency(r), sessionID(r))
p, err := fe.getProduct(r.Context(), id) p, err := fe.getProduct(r.Context(), id)
if err != nil { 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 return
} }
currencies, err := fe.getCurrencies(r.Context()) currencies, err := fe.getCurrencies(r.Context())
if err != nil { 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 return
} }
cart, err := fe.getCart(r.Context(), sessionID(r)) cart, err := fe.getCart(r.Context(), sessionID(r))
if err != nil { 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 return
} }
price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))
if err != nil { 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 return
} }
recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), []string{id}) recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), []string{id})
if err != nil { 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 return
} }
@ -147,19 +148,19 @@ func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Reques
quantity, _ := strconv.ParseUint(r.FormValue("quantity"), 10, 32) quantity, _ := strconv.ParseUint(r.FormValue("quantity"), 10, 32)
productID := r.FormValue("product_id") productID := r.FormValue("product_id")
if productID == "" || quantity == 0 { if productID == "" || quantity == 0 {
renderHTTPError(w, fmt.Errorf("invalid form input"), http.StatusBadRequest) renderHTTPError(w, errors.New("invalid form input"), http.StatusBadRequest)
return return
} }
log.Printf("[addToCart] product_id=%s qty=%d session_id=%s", productID, quantity, sessionID(r)) log.Printf("[addToCart] product_id=%s qty=%d session_id=%s", productID, quantity, sessionID(r))
p, err := fe.getProduct(r.Context(), productID) p, err := fe.getProduct(r.Context(), productID)
if err != nil { 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 return
} }
if err := fe.insertCart(r.Context(), sessionID(r), p.GetId(), int32(quantity)); err != nil { 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 return
} }
w.Header().Set("location", "/cart") 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)) log.Printf("[emptyCart] session_id=%s", sessionID(r))
if err := fe.emptyCart(r.Context(), sessionID(r)); err != nil { 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 return
} }
w.Header().Set("location", "/") 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)) log.Printf("[viewCart] session_id=%s", sessionID(r))
currencies, err := fe.getCurrencies(r.Context()) currencies, err := fe.getCurrencies(r.Context())
if err != nil { 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 return
} }
cart, err := fe.getCart(r.Context(), sessionID(r)) cart, err := fe.getCart(r.Context(), sessionID(r))
if err != nil { 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 return
} }
recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), cartIDs(cart)) recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), cartIDs(cart))
if err != nil { 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 return
} }
shippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r)) shippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r))
if err != nil { 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 return
} }
@ -212,12 +213,12 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request
for i, item := range cart { for i, item := range cart {
p, err := fe.getProduct(r.Context(), item.GetProductId()) p, err := fe.getProduct(r.Context(), item.GetProductId())
if err != nil { 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 return
} }
price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r))
if err != nil { 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 return
} }
@ -280,7 +281,7 @@ func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Reque
Country: country}, Country: country},
}) })
if err != nil { 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 return
} }
log.Printf("order #%s completed", order.GetOrder().GetOrderId()) 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) { 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) w.WriteHeader(code)
templates.ExecuteTemplate(w, "error", map[string]interface{}{ templates.ExecuteTemplate(w, "error", map[string]interface{}{
"error": err.Error(), "error": errMsg,
"status_code": code, "status_code": code,
"status": http.StatusText(code)}) "status": http.StatusText(code)})
} }

View file

@ -6,6 +6,9 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"time"
"github.com/pkg/errors"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -56,6 +59,7 @@ func main() {
if os.Getenv("PORT") != "" { if os.Getenv("PORT") != "" {
srvPort = os.Getenv("PORT") srvPort = os.Getenv("PORT")
} }
addr := os.Getenv("LISTEN_ADDR")
svc := new(frontendServer) svc := new(frontendServer)
mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR") mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR")
mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR") mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR")
@ -64,31 +68,12 @@ func main() {
mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR") mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR")
mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR")
var err error mustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr)
svc.currencySvcConn, err = grpc.DialContext(ctx, svc.currencySvcAddr, grpc.WithInsecure()) mustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr)
if err != nil { mustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr)
log.Fatalf("failed to connect currency service: %+v", err) mustConnGRPC(ctx, &svc.recommendationSvcConn, svc.recommendationSvcAddr)
} mustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr)
svc.productCatalogSvcConn, err = grpc.DialContext(ctx, svc.productCatalogSvcAddr, grpc.WithInsecure()) mustConnGRPC(ctx, &svc.checkoutSvcConn, svc.checkoutSvcAddr)
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)
}
r := mux.NewRouter() r := mux.NewRouter()
r.HandleFunc("/", ensureSessionID(svc.homeHandler)).Methods(http.MethodGet, http.MethodHead) 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("/logout", svc.logoutHandler).Methods(http.MethodGet)
r.HandleFunc("/cart/checkout", ensureSessionID(svc.placeOrderHandler)).Methods(http.MethodPost) r.HandleFunc("/cart/checkout", ensureSessionID(svc.placeOrderHandler)).Methods(http.MethodPost)
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
log.Printf("starting server on :" + srvPort) log.Printf("starting server on " + addr + ":" + srvPort)
log.Fatal(http.ListenAndServe(":"+srvPort, r)) log.Fatal(http.ListenAndServe(addr+":"+srvPort, r))
} }
func mustMapEnv(target *string, envKey string) { func mustMapEnv(target *string, envKey string) {
@ -111,3 +96,11 @@ func mustMapEnv(target *string, envKey string) {
} }
*target = v *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))
}
}

View file

@ -2,13 +2,10 @@ package main
import ( import (
"context" "context"
"fmt"
"google.golang.org/grpc/codes"
pb "frontend/genproto" pb "frontend/genproto"
"google.golang.org/grpc/status" "github.com/pkg/errors"
) )
const ( 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) { func (fe *frontendServer) getCart(ctx context.Context, userID string) ([]*pb.CartItem, error) {
resp, err := pb.NewCartServiceClient(fe.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID}) 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 return resp.GetItems(), err
} }
@ -85,10 +78,7 @@ func (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.Cart
return nil, err return nil, err
} }
localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency) localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency)
if err != nil { return localized, errors.Wrap(err, "failed to convert currency for shipping cost")
return nil, fmt.Errorf("failed to convert currency for shipping cost: %+v", err)
}
return localized, nil
} }
func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) { 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() { for i, v := range resp.GetProductIds() {
p, err := fe.getProduct(ctx, v) p, err := fe.getProduct(ctx, v)
if err != nil { 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 out[i] = p
} }

View file

@ -8,7 +8,7 @@
<p>Something has failed. Below are some details for debugging.</p> <p>Something has failed. Below are some details for debugging.</p>
<p><strong>HTTP Status:</strong> {{.status_code}} {{.status}}</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;"> style="white-space: pre-wrap; word-break: keep-all;">
{{- .error -}} {{- .error -}}
</pre> </pre>