Skip to content

Commit

Permalink
add span attributes to frontend (open-telemetry#82)
Browse files Browse the repository at this point in the history
* add manual instrumentation to frontend

* frontend span attributes
  • Loading branch information
puckpuck authored Jun 3, 2022
1 parent 0ee3a27 commit aa0d821
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 8 deletions.
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

0 comments on commit aa0d821

Please sign in to comment.