The frontend service is responsible for rendering the UI for the store's website. It serves as the main entry point for the application routing requests to their appropriate backend services. The application uses Server Side Rendering (SSR) to generate HTML consumed by the clients, which could be web browsers, web crawlers, mobile clients or something else.
- Completed: 💯
- Not Present (Yet): 🔴
- Instrumentation Libraries: :100:
- Manual Span Creation: :red_circle:
- Span Data Enrichment: :100:
- Interprocess Context Propagation: 💯
- Intra-service Context Propagation: :red_circle:
- Trace Links: :red_circle:
- Baggage: :red_circle:
- Instrumentation Libraries: :red_circle:
- Manual Metric Creation: :red_circle:
- Collector Agent Metric Transformation::red_circle:
- Push Metrics: :red_circle:
- SLO Metrics: 🔴
- Multiple Manual Metric Instruments: :red_circle:
The OpenTelemetry SDK is initialized in main
using the InitTraceProvider
function.
func InitTracerProvider() *sdktrace.TracerProvider {
ctx := context.Background()
exporter, err := otlptracegrpc.New(ctx)
if err != nil {
log.Fatal(err)
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp
}
Services should call TraceProvider.Shutdown()
when the service is shutdown to
ensure all spans are exported.
This service makes that call as part of a deferred function in main
.
// Initialize OpenTelemetry Tracing
tp := InitTracerProvider()
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Printf("Error shutting down tracer provider: %v", err)
}
}()
This service receives HTTP requests, controlled by the gorilla/mux Router. The following routes are defined by the frontend:
Path | Method | Use |
---|---|---|
/ |
GET | Main index page |
/cart |
GET | View Cart |
/cart |
POST | Add to Cart |
/cart/checkout |
POST | Place Order |
/cart/empty |
POST | Empty Cart |
/logout |
GET | Logout |
/product/{id} |
GET | View Product |
/setCurrency |
POST | Set Currency |
/static/ |
* | Static resources |
/robots.txt |
* | Search engine response (disallow) |
/_healthz |
* | Health check (ok) |
These requests are instrumented in the main function as part of the router's definition.
// Add OpenTelemetry instrumentation to incoming HTTP requests controlled by the gorilla/mux Router.
r.Use(otelmux.Middleware("server"))
This service will issue several outgoing gRPC calls, which have instrumentation
hooks added in the mustConnGRPC
function.
func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {
// Add OpenTelemetry instrumentation to outgoing gRPC requests
var err error
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
defer cancel()
*conn, err = grpc.DialContext(ctx, addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
)
if err != nil {
panic(errors.Wrapf(err, "grpc: failed to connect %s", addr))
}
}
All requests incoming to the frontend service will receive the following attributes:
app.session.id
app.request.id
app.currency
app.user.id
(when the user is present)
These attributes are added in the instrumentHandler
function (defined in the
middleware.go file) which wraps all HTTP routes specified within the
gorilla/mux router.
Additional attributes are added within each handler's function as appropriate
(ie: app.cart.size
, app.cart.total.price
).
Adding attributes to existing auto-instrumented spans can be accomplished by getting the current span from context, then adding attributes to it.
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),
)
When an error is encountered, the current span's status code and error message are set.
// set span status on error
span := trace.SpanFromContext(r.Context())
span.SetStatus(codes.Error, errMsg)