Skip to content

Commit

Permalink
feat: instrument http server and client
Browse files Browse the repository at this point in the history
  • Loading branch information
ernado committed Apr 4, 2023
1 parent ead1f04 commit 4d6102e
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 1 deletion.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/prometheus/client_golang v1.14.0
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.2
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0
go.opentelemetry.io/contrib/instrumentation/runtime v0.40.0
go.opentelemetry.io/contrib/propagators/autoprop v0.40.0
go.opentelemetry.io/otel v1.14.0
Expand Down Expand Up @@ -36,6 +37,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.8.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-faster/yaml v0.4.3 // indirect
github.com/go-logr/logr v1.2.4 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
Expand Down Expand Up @@ -212,6 +214,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI=
go.opentelemetry.io/contrib/instrumentation/runtime v0.40.0 h1:Qf1GuR3QFxTNqDhfuw9XuJMkOOyRUwWP9NdFakk3RXM=
go.opentelemetry.io/contrib/instrumentation/runtime v0.40.0/go.mod h1:zmll4G8j5zRZeFURG6t/N7SOl7M5kUHQfV5UVqTaQFI=
go.opentelemetry.io/contrib/propagators/autoprop v0.40.0 h1:Lj33jj7eIrBfIShiK8NU91u2BglKnUS1UUxVemuQJtw=
Expand Down
113 changes: 113 additions & 0 deletions internal/app/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package app

import (
"fmt"
"net/http"
"time"

"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"

"github.com/go-faster/simon/internal/middleware"
"github.com/go-faster/simon/internal/oas"
"github.com/go-faster/simon/sdk/zctx"
)

type writerProxy struct {
http.ResponseWriter

wrote int64
status int
}

func (w *writerProxy) Write(bytes []byte) (n int, err error) {
n, err = w.ResponseWriter.Write(bytes)
w.wrote += int64(n)
return n, err
}
func (w *writerProxy) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
w.status = statusCode
}

type Router interface {
FindRoute(method, path string) (oas.Route, bool)
}

func NewSpanNameFormatter(h Router) func(operation string, r *http.Request) string {
return func(operation string, r *http.Request) string {
route, ok := h.FindRoute(r.Method, r.URL.Path)
if !ok {
return operation
}
return route.OperationID()
}
}

// TraceMiddleware returns new instrumented middleware.
func (m *Metrics) TraceMiddleware() middleware.Middleware {
var (
h = oas.Server{}
p = m.TextMapPropagator()
tp = m.TracerProvider()
)
return func(next http.Handler) http.Handler {
t := tp.Tracer("http")
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
start := time.Now()
w := &writerProxy{ResponseWriter: rw}

operation := "(Unknown)"
route, routeOk := h.FindRoute(r.Method, r.URL.Path)
if routeOk {
operation = route.OperationID()
}

ctx := p.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
ctx, span := t.Start(ctx, fmt.Sprintf("HTTP: %s", operation))
defer span.End()
spanCtx := span.SpanContext()

// Use separate loggers for request logger (to log in defer) and
// context logger, because context logger should not contain
// trace_id and span_id to be able to change them for new
// spans.
lgCtx := m.lg
fields := []zap.Field{
zap.Stringer("trace_id", spanCtx.TraceID()),
zap.Stringer("span_id", spanCtx.SpanID()),
}
if routeOk {
f := zap.String("op", operation)
fields = append(fields, f)
lgCtx = lgCtx.With(f)
}
lgReq := m.lg.With(fields...)
ctx = zctx.With(ctx, lgCtx)

defer func() {
if r := recover(); r != nil {
lgReq.Error("Panic", zap.Stack("stack"))
if w.status == 0 {
w.WriteHeader(http.StatusInternalServerError)
}
span.AddEvent("Panic recovered",
trace.WithStackTrace(true),
)
span.SetStatus(codes.Error, "Panic recovered")
}
lgReq.Debug("Request",
zap.Duration("duration", time.Since(start)),
zap.Int("http.status", w.status),
zap.Int64("http.response.size", w.wrote),
zap.String("http.path", r.URL.String()),
zap.String("http.method", r.Method),
)
}()

next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
11 changes: 11 additions & 0 deletions internal/cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package cmd

import (
"context"
"net/http"
"os"
"time"

"github.com/go-faster/errors"
"github.com/spf13/cobra"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.uber.org/zap"

"github.com/go-faster/simon/internal/app"
Expand All @@ -24,9 +26,18 @@ func cmdClient() *cobra.Command {
if addr == "" {
addr = "http://localhost:8080"
}
spanNameFormatter := app.NewSpanNameFormatter(&oas.Server{})
c, err := oas.NewClient(addr,
oas.WithMeterProvider(m.MeterProvider()),
oas.WithTracerProvider(m.TracerProvider()),
oas.WithClient(&http.Client{
Timeout: time.Second * 2,
Transport: otelhttp.NewTransport(http.DefaultTransport,
otelhttp.WithSpanNameFormatter(spanNameFormatter),
otelhttp.WithMeterProvider(m.MeterProvider()),
otelhttp.WithTracerProvider(m.TracerProvider()),
),
}),
)
if err != nil {
return errors.Wrap(err, "client")
Expand Down
9 changes: 8 additions & 1 deletion internal/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/go-faster/errors"
"github.com/spf13/cobra"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"

Expand All @@ -33,12 +34,18 @@ func cmdServer() *cobra.Command {
if err != nil {
return err
}

spanNameFormatter := app.NewSpanNameFormatter(h)
s := &http.Server{
Addr: addr,
ReadHeaderTimeout: time.Second,
WriteTimeout: time.Second,
ReadTimeout: time.Second,
Handler: h,
Handler: otelhttp.NewHandler(h, "",
otelhttp.WithSpanNameFormatter(spanNameFormatter),
otelhttp.WithMeterProvider(m.MeterProvider()),
otelhttp.WithTracerProvider(m.TracerProvider()),
),
}

lg.Info("Starting HTTP server", zap.String("addr", addr))
Expand Down
12 changes: 12 additions & 0 deletions internal/middleware/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Package middleware is http middleware.
package middleware

import "net/http"

// Middleware is http middleware.
type Middleware func(next http.Handler) http.Handler

// Wrap Middleware.
func Wrap(h http.Handler, mw Middleware) http.Handler {
return mw(h)
}

0 comments on commit 4d6102e

Please sign in to comment.