Skip to content

Commit 47f5396

Browse files
committed
crpc telemetry middleware
1 parent 32f039e commit 47f5396

File tree

3 files changed

+47
-5
lines changed

3 files changed

+47
-5
lines changed

libraries/crpc/client.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/0xdeafcafe/bloefish/libraries/errfuncs"
1111
"github.com/0xdeafcafe/bloefish/libraries/jsonclient"
1212
"github.com/0xdeafcafe/bloefish/libraries/version"
13+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
1314
)
1415

1516
const (
@@ -26,8 +27,15 @@ type Client struct {
2627
// NewClient returns a client configured with a transport scheme, remote host
2728
// and URL prefix supplied as a URL <scheme>://<host></prefix>
2829
func NewClient(ctx context.Context, baseURL string, c *http.Client) *Client {
29-
jcc := jsonclient.NewClient(baseURL, c)
30+
if c == nil {
31+
c = &http.Client{
32+
Transport: otelhttp.NewTransport(http.DefaultTransport, otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
33+
return fmt.Sprintf("crpc %s%s", r.URL.Hostname(), r.URL.Path)
34+
})),
35+
}
36+
}
3037

38+
jcc := jsonclient.NewClient(baseURL, c)
3139
svc := contexts.GetServiceInfo(ctx)
3240
if svc != nil {
3341
jcc.UserAgent = fmt.Sprintf(userAgentTemplateWithService, version.Truncated, svc.Service, svc.Environment)

libraries/crpc/middlewares/logger.go renamed to libraries/crpc/middlewares/telemetry.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ package middlewares
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67
"time"
78

89
"github.com/0xdeafcafe/bloefish/libraries/cher"
910
"github.com/0xdeafcafe/bloefish/libraries/clog"
11+
"github.com/0xdeafcafe/bloefish/libraries/contexts"
1012
"github.com/0xdeafcafe/bloefish/libraries/errfuncs"
1113
"github.com/0xdeafcafe/bloefish/libraries/merr"
1214
"github.com/0xdeafcafe/bloefish/libraries/mlog"
1315
"github.com/0xdeafcafe/bloefish/libraries/slicefuncs"
1416
"github.com/sirupsen/logrus"
17+
"go.opentelemetry.io/otel"
18+
"go.opentelemetry.io/otel/propagation"
19+
"go.opentelemetry.io/otel/semconv/v1.13.0/httpconv"
20+
"go.opentelemetry.io/otel/trace"
21+
22+
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
1523
)
1624

1725
type responseWriter struct {
@@ -39,7 +47,7 @@ func (rw *responseWriter) Write(bytes []byte) (int, error) {
3947
return rw.ResponseWriter.Write(bytes)
4048
}
4149

42-
// Logger returns a middleware handler that wraps subsequent middleware/handlers and logs
50+
// Telemetry returns a middleware handler that wraps subsequent middleware/handlers and logs
4351
// request information AFTER the request has completed. It also injects a request-scoped
4452
// logger on the context which can be set, read and updated using clog lib
4553
//
@@ -57,10 +65,27 @@ func (rw *responseWriter) Write(bytes []byte) (int, error) {
5765
// - Response in bytes (http_response_bytes)
5866
// - Client Version header (http_client_version)
5967
// - User Agent header (http_user_agent)
60-
func Logger(log *logrus.Entry) func(http.Handler) http.Handler {
68+
func Telemetry(log *logrus.Entry) func(http.Handler) http.Handler {
69+
propagator := otel.GetTextMapPropagator()
70+
tracer := otel.Tracer(
71+
"github.com/0xdeafcafe/bloefish/libraries/crpc",
72+
)
73+
6174
return func(next http.Handler) http.Handler {
6275
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63-
ctx := r.Context()
76+
ctx, span := tracer.Start(
77+
propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header)),
78+
"crpc", // Placeholder, this will be updates below
79+
trace.WithSpanKind(trace.SpanKindServer),
80+
)
81+
defer span.End()
82+
83+
// inject the span context into the response headers
84+
propagator.Inject(ctx, propagation.HeaderCarrier(w.Header()))
85+
86+
// update the span name with the service name and endpoint
87+
svcCtx := contexts.MustGetServiceInfo(ctx)
88+
span.SetName(fmt.Sprintf("crpc (%s%s)", svcCtx.ServiceHTTPName, r.URL.Path))
6489

6590
// create a mutable logger instance which will persist for the request
6691
// inject pointer to the logger into the request context
@@ -70,6 +95,7 @@ func Logger(log *logrus.Entry) func(http.Handler) http.Handler {
7095
// panics inside handlers will be logged to standard before propagation
7196
defer clog.HandlePanic(ctx, true)
7297

98+
// set useful log fields for the request
7399
clog.SetFields(ctx, clog.Fields{
74100
"http_remote_addr": r.RemoteAddr,
75101
"http_user_agent": r.UserAgent(),
@@ -87,18 +113,26 @@ func Logger(log *logrus.Entry) func(http.Handler) http.Handler {
87113
next.ServeHTTP(res, r)
88114
tEnd := time.Now()
89115

116+
// set useful log fields from the response
90117
clog.SetFields(ctx, clog.Fields{
91118
"http_duration": tEnd.Sub(tStart).String(),
92119
"http_duration_us": int64(tEnd.Sub(tStart) / time.Microsecond),
93120
"http_status": res.Status,
94121
"http_response_bytes": res.Bytes,
95122
})
96123

124+
// set the span attributes and status
125+
span.SetAttributes(semconv.HTTPResponseStatusCode(res.Status))
126+
span.SetStatus(httpconv.ServerStatus(res.Status))
127+
128+
// get the error if one is set on the log entry
97129
err := getError(clog.Get(ctx))
98130
if err == nil {
99131
mlog.Info(ctx, merr.New(ctx, "request_completed", nil))
100132
return
101133
}
134+
135+
// if appropriate, log the error at the appropriate level
102136
var fn func(context.Context, merr.Merrer)
103137
switch clog.DetermineLevel(err, clog.TimeoutsAsErrors(ctx)) {
104138
case

libraries/crpc/middlewares/logger_test.go renamed to libraries/crpc/middlewares/telemetry_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func TestLogger(t *testing.T) {
7777
is.NoErr(err)
7878
})
7979

80-
mw := Logger(log)
80+
mw := Telemetry(log)
8181
is.True(mw != nil)
8282

8383
fn := mw(next)

0 commit comments

Comments
 (0)