Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add span attributes to frontend #82

Merged
merged 2 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ release.
future significant modifications will be credited to OpenTelemetry Authors.
* Added feature flag service protos
([#26](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/26))
* Added span attributes to frontend service
([#82](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/82))
48 changes: 48 additions & 0 deletions src/frontend/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ package main
import (
"context"
"fmt"
"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/instr"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"html/template"
"math/rand"
"net"
Expand Down Expand Up @@ -102,6 +106,12 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
plat = platformDetails{}
plat.setPlatformDetails(strings.ToLower(env))

span := trace.SpanFromContext(r.Context())
span.SetAttributes(
attribute.Int(instr.AppPrefix+"products.count", len(products)),
attribute.Int(instr.AppPrefix+"cart.size", cartSize(cart)),
)

if err := templates.ExecuteTemplate(w, "home", map[string]interface{}{
"session_id": sessionID(r),
"request_id": r.Context().Value(ctxKeyRequestID{}),
Expand All @@ -117,6 +127,7 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
"is_cymbal_brand": isCymbalBrand,
"deploymentDetails": deploymentDetailsMap,
}); err != nil {
span.SetStatus(codes.Error, err.Error())
log.Error(err)
}
}
Expand Down Expand Up @@ -187,6 +198,12 @@ func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request)
Price *pb.Money
}{p, price}

span := trace.SpanFromContext(r.Context())
span.SetAttributes(
attribute.String(instr.AppPrefix+"product.id", id),
attribute.Int(instr.AppPrefix+"cart.size", cartSize(cart)),
)

if err := templates.ExecuteTemplate(w, "product", map[string]interface{}{
"session_id": sessionID(r),
"request_id": r.Context().Value(ctxKeyRequestID{}),
Expand All @@ -202,6 +219,7 @@ func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request)
"is_cymbal_brand": isCymbalBrand,
"deploymentDetails": deploymentDetailsMap,
}); err != nil {
span.SetStatus(codes.Error, err.Error())
log.Println(err)
}
}
Expand All @@ -222,6 +240,12 @@ func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Reques
return
}

span := trace.SpanFromContext(r.Context())
span.SetAttributes(
attribute.String(instr.AppPrefix+"product.id", productID),
attribute.Int(instr.AppPrefix+"product.quantity", int(quantity)),
)

if err := fe.insertCart(r.Context(), sessionID(r), p.GetId(), int32(quantity)); err != nil {
renderHTTPError(log, r, w, errors.Wrap(err, "failed to add to cart"), http.StatusInternalServerError)
return
Expand Down Expand Up @@ -297,6 +321,17 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request
totalPrice = money.Must(money.Sum(totalPrice, shippingCost))
year := time.Now().Year()

// add cart details to span as a manually created attributes
shippingCostFloat, _ := strconv.ParseFloat(fmt.Sprintf("%d.%02d", shippingCost.GetUnits(), shippingCost.GetNanos()/10000000), 64)
totalPriceFloat, _ := strconv.ParseFloat(fmt.Sprintf("%d.%02d", totalPrice.GetUnits(), totalPrice.GetNanos()/10000000), 64)
span := trace.SpanFromContext(r.Context())
span.SetAttributes(
attribute.Int(instr.AppPrefix+"cart.size", cartSize(cart)),
attribute.Int(instr.AppPrefix+"cart.items.count", len(items)),
attribute.Float64(instr.AppPrefix+"cart.shipping.cost", shippingCostFloat),
attribute.Float64(instr.AppPrefix+"cart.total.price", totalPriceFloat),
)

if err := templates.ExecuteTemplate(w, "cart", map[string]interface{}{
"session_id": sessionID(r),
"request_id": r.Context().Value(ctxKeyRequestID{}),
Expand All @@ -314,6 +349,7 @@ func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request
"is_cymbal_brand": isCymbalBrand,
"deploymentDetails": deploymentDetailsMap,
}); err != nil {
span.SetStatus(codes.Error, err.Error())
log.Println(err)
}
}
Expand Down Expand Up @@ -367,6 +403,11 @@ func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Reque
totalPaid = money.Must(money.Sum(totalPaid, multPrice))
}

// add Order total paid to span as a manually created attribute
totalPaidFloat, _ := strconv.ParseFloat(fmt.Sprintf("%d.%02d", totalPaid.GetUnits(), totalPaid.GetNanos()/10000000), 64)
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attribute.Float64(instr.AppPrefix+"order.total", totalPaidFloat))

currencies, err := fe.getCurrencies(r.Context())
if err != nil {
renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError)
Expand All @@ -387,6 +428,7 @@ func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Reque
"is_cymbal_brand": isCymbalBrand,
"deploymentDetails": deploymentDetailsMap,
}); err != nil {
span.SetStatus(codes.Error, err.Error())
log.Println(err)
}
}
Expand All @@ -410,6 +452,8 @@ func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Requ
Debug("setting currency")

if cur != "" {
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attribute.String(instr.AppPrefix+"currency.new", cur))
http.SetCookie(w, &http.Cookie{
Name: cookieCurrency,
Value: cur,
Expand Down Expand Up @@ -439,6 +483,10 @@ func renderHTTPError(log logrus.FieldLogger, r *http.Request, w http.ResponseWri
log.WithField("error", err).Error("request error")
errMsg := fmt.Sprintf("%+v", err)

// set span status on error
span := trace.SpanFromContext(r.Context())
span.SetStatus(codes.Error, errMsg)

w.WriteHeader(code)

if templateErr := templates.ExecuteTemplate(w, "error", map[string]interface{}{
Expand Down
13 changes: 13 additions & 0 deletions src/frontend/instr/conventions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package instr

import "go.opentelemetry.io/otel/attribute"

const AppPrefix = "app."

const (
SessionId = attribute.Key(AppPrefix + "session.id")
RequestId = attribute.Key(AppPrefix + "request.id")
UserId = attribute.Key(AppPrefix + "user.id")

Currency = attribute.Key(AppPrefix + "currency")
)
16 changes: 8 additions & 8 deletions src/frontend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ func main() {

r := mux.NewRouter()
r.Use(otelmux.Middleware("server"))
r.HandleFunc("/", svc.homeHandler).Methods(http.MethodGet, http.MethodHead)
r.HandleFunc("/product/{id}", svc.productHandler).Methods(http.MethodGet, http.MethodHead)
r.HandleFunc("/cart", svc.viewCartHandler).Methods(http.MethodGet, http.MethodHead)
r.HandleFunc("/cart", svc.addToCartHandler).Methods(http.MethodPost)
r.HandleFunc("/cart/empty", svc.emptyCartHandler).Methods(http.MethodPost)
r.HandleFunc("/setCurrency", svc.setCurrencyHandler).Methods(http.MethodPost)
r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet)
r.HandleFunc("/cart/checkout", svc.placeOrderHandler).Methods(http.MethodPost)
r.HandleFunc("/", instrumentHandler(svc.homeHandler)).Methods(http.MethodGet, http.MethodHead)
r.HandleFunc("/product/{id}", instrumentHandler(svc.productHandler)).Methods(http.MethodGet, http.MethodHead)
r.HandleFunc("/cart", instrumentHandler(svc.viewCartHandler)).Methods(http.MethodGet, http.MethodHead)
r.HandleFunc("/cart", instrumentHandler(svc.addToCartHandler)).Methods(http.MethodPost)
r.HandleFunc("/cart/empty", instrumentHandler(svc.emptyCartHandler)).Methods(http.MethodPost)
r.HandleFunc("/setCurrency", instrumentHandler(svc.setCurrencyHandler)).Methods(http.MethodPost)
r.HandleFunc("/logout", instrumentHandler(svc.logoutHandler)).Methods(http.MethodGet)
r.HandleFunc("/cart/checkout", instrumentHandler(svc.placeOrderHandler)).Methods(http.MethodPost)
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
r.HandleFunc("/robots.txt", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "User-agent: *\nDisallow: /") })
r.HandleFunc("/_healthz", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "ok") })
Expand Down
30 changes: 30 additions & 0 deletions src/frontend/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package main

import (
"context"
"github.com/GoogleCloudPlatform/microservices-demo/src/frontend/instr"
"go.opentelemetry.io/otel/trace"
"net/http"
"time"

Expand All @@ -26,6 +28,8 @@ import (
type ctxKeyLog struct{}
type ctxKeyRequestID struct{}

type httpHandler func(w http.ResponseWriter, r *http.Request)

type logHandler struct {
log *logrus.Logger
next http.Handler
Expand Down Expand Up @@ -81,6 +85,32 @@ func (lh *logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
lh.next.ServeHTTP(rr, r)
}

func instrumentHandler(fn httpHandler) httpHandler {
// Add common attributes to the span for each handler
// session, request, currency, and user

return func(w http.ResponseWriter, r *http.Request) {
rid := r.Context().Value(ctxKeyRequestID{})
requestID := ""
if rid != nil {
requestID = rid.(string)
}
span := trace.SpanFromContext(r.Context())
span.SetAttributes(
instr.SessionId.String(sessionID(r)),
instr.RequestId.String(requestID),
instr.Currency.String(currentCurrency(r)),
)

email := r.FormValue("email")
if email != "" {
span.SetAttributes(instr.UserId.String(email))
}

fn(w, r)
}
}

func ensureSessionID(next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var sessionID string
Expand Down