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"
- name: LISTEN_ADDR
value: "0.0.0.0"
- name: GRPC_TRACE
value: "all"
- name: GRPC_VERBOSITY
value: "debug"
resources:
requests:
cpu: 200m

View file

@ -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"]

View file

@ -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)})
}

View file

@ -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))
}
}

View file

@ -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
}

View file

@ -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>