Skip to content

Commit e0e433c

Browse files
committed
add telemetry pkg
1 parent 9249229 commit e0e433c

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

telemetry/exporter.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package telemetry
2+
3+
import (
4+
"context"
5+
6+
"go.opentelemetry.io/otel"
7+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
8+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
9+
"go.opentelemetry.io/otel/sdk/resource"
10+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
11+
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
12+
"golang.org/x/xerrors"
13+
)
14+
15+
// Exporter creates a grpc otlp exporter and sets it as the global trace provider.
16+
// Caller is responsible for closing exporter to ensure all data is flushed.
17+
func Exporter(ctx context.Context, service string) (func(), error) {
18+
res, err := resource.New(ctx,
19+
resource.WithAttributes(
20+
// the service name used to display traces in backends
21+
semconv.ServiceNameKey.String(service),
22+
),
23+
)
24+
25+
otlptracegrpc.NewClient()
26+
exporter, err := otlptrace.New(ctx, otlptracegrpc.NewClient())
27+
if err != nil {
28+
return nil, xerrors.Errorf("creating otlp exporter: %w", err)
29+
}
30+
31+
tracerProvider := sdktrace.NewTracerProvider(
32+
sdktrace.WithBatcher(exporter),
33+
sdktrace.WithResource(res),
34+
)
35+
otel.SetTracerProvider(tracerProvider)
36+
37+
return func() {
38+
_ = tracerProvider.Shutdown(ctx)
39+
}, nil
40+
}

telemetry/httpmw.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package telemetry
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/go-chi/chi/middleware"
8+
"github.com/go-chi/chi/v5"
9+
"go.opentelemetry.io/otel"
10+
"go.opentelemetry.io/otel/attribute"
11+
"go.opentelemetry.io/otel/codes"
12+
)
13+
14+
// HTTPMW adds tracing to http routes.
15+
func HTTPMW(tracer string) func(http.Handler) http.Handler {
16+
return func(next http.Handler) http.Handler {
17+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
18+
// start span with default span name. Span name will be updated once request finishes
19+
_, span := otel.Tracer(tracer).Start(r.Context(), "http.request")
20+
defer span.End()
21+
22+
wrw := middleware.NewWrapResponseWriter(rw, r.ProtoMajor)
23+
24+
// pass the span through the request context and serve the request to the next middleware
25+
next.ServeHTTP(rw, r)
26+
27+
// set the resource name as we get it only once the handler is executed
28+
resourceName := chi.RouteContext(r.Context()).RoutePattern()
29+
if resourceName == "" {
30+
resourceName = "unknown"
31+
}
32+
resourceName = r.Method + " " + resourceName
33+
span.SetName(resourceName)
34+
35+
// set the status code
36+
status := wrw.Status()
37+
// 0 status means one has not yet been sent in which case net/http library will write StatusOK
38+
if status == 0 {
39+
status = http.StatusOK
40+
}
41+
span.SetAttributes(attribute.KeyValue{
42+
Key: "http.status_code",
43+
Value: attribute.IntValue(status),
44+
})
45+
46+
// if 5XX we set the span to "error" status
47+
if status >= 500 {
48+
span.SetStatus(codes.Error, fmt.Sprintf("%d: %s", status, http.StatusText(status)))
49+
}
50+
})
51+
}
52+
}

0 commit comments

Comments
 (0)