Added OpenTelemetry and use structured logging
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/robindittmar/dttmr-api/internal/api/response"
|
||||
@@ -18,15 +18,17 @@ type apiResponse struct {
|
||||
}
|
||||
|
||||
func DefaultHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var resp apiResponse
|
||||
ctx := r.Context()
|
||||
|
||||
resp.Method = r.Method
|
||||
resp.Url = r.URL.String()
|
||||
resp.Proto = r.Proto
|
||||
resp.Header = make(map[string]string)
|
||||
resp.Host = r.Host
|
||||
resp.RemoteAddr = r.RemoteAddr
|
||||
resp.Form = make(map[string]string)
|
||||
resp := apiResponse{
|
||||
Method: r.Method,
|
||||
Url: r.URL.String(),
|
||||
Proto: r.Proto,
|
||||
Header: make(map[string]string),
|
||||
Host: r.Host,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
Form: make(map[string]string),
|
||||
}
|
||||
|
||||
for k, v := range r.Header {
|
||||
resp.Header[k] = v[0]
|
||||
@@ -37,8 +39,8 @@ func DefaultHandler(w http.ResponseWriter, r *http.Request) {
|
||||
resp.Form[k] = v[0]
|
||||
}
|
||||
} else {
|
||||
log.Println(err)
|
||||
slog.ErrorContext(ctx, "Error parsing form", slog.Any("error", err))
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, resp)
|
||||
response.JSON(ctx, w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ type healthResponse struct {
|
||||
}
|
||||
|
||||
func HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r
|
||||
ctx := r.Context()
|
||||
|
||||
resp := healthResponse{
|
||||
Status: "ok",
|
||||
}
|
||||
response.JSON(w, http.StatusOK, resp)
|
||||
response.JSON(ctx, w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
func WithTelemetry(next http.Handler) http.Handler {
|
||||
//return otelhttp.NewHandler(next, "dttmr-api")
|
||||
return next
|
||||
return otelhttp.NewHandler(next, "dttmr-api")
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func JSON(w http.ResponseWriter, status int, data any) {
|
||||
func JSON(ctx context.Context, w http.ResponseWriter, status int, data any) {
|
||||
payload, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, err := w.Write([]byte(`{"error": "internal server error: failed to marshal response"}`))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
slog.ErrorContext(ctx, "Failed to write JSON", slog.Any("error", err))
|
||||
return
|
||||
}
|
||||
return
|
||||
@@ -23,11 +24,11 @@ func JSON(w http.ResponseWriter, status int, data any) {
|
||||
w.WriteHeader(status)
|
||||
_, err = w.Write(payload)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
slog.ErrorContext(ctx, "Failed to write response", slog.Any("error", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Error(w http.ResponseWriter, status int, message string) {
|
||||
JSON(w, status, map[string]string{"error": message})
|
||||
func Error(ctx context.Context, w http.ResponseWriter, status int, message string) {
|
||||
JSON(ctx, w, status, map[string]string{"error": message})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type TraceHandler struct {
|
||||
slog.Handler
|
||||
}
|
||||
|
||||
func (h *TraceHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if spanCtx.HasTraceID() {
|
||||
r.AddAttrs(
|
||||
slog.String("trace_id", spanCtx.TraceID().String()),
|
||||
slog.String("span_id", spanCtx.SpanID().String()),
|
||||
)
|
||||
}
|
||||
|
||||
return h.Handler.Handle(ctx, r)
|
||||
}
|
||||
|
||||
func (h *TraceHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &TraceHandler{Handler: h.Handler.WithAttrs(attrs)}
|
||||
}
|
||||
|
||||
func (h *TraceHandler) WithGroup(name string) slog.Handler {
|
||||
return &TraceHandler{Handler: h.Handler.WithGroup(name)}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
)
|
||||
|
||||
func Init(ctx context.Context, serviceName string, version string) (func(context.Context) error, error) {
|
||||
var shutdownFns []func(context.Context) error
|
||||
|
||||
shutdown := func(ctx context.Context) error {
|
||||
var err error
|
||||
for _, fn := range shutdownFns {
|
||||
err = errors.Join(err, fn(ctx))
|
||||
}
|
||||
shutdownFns = nil
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := resource.New(ctx,
|
||||
resource.WithAttributes(
|
||||
semconv.ServiceName(serviceName),
|
||||
semconv.ServiceVersion(version),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return shutdown, err
|
||||
}
|
||||
|
||||
otel.SetTextMapPropagator(
|
||||
propagation.NewCompositeTextMapPropagator(
|
||||
propagation.TraceContext{},
|
||||
propagation.Baggage{},
|
||||
),
|
||||
)
|
||||
|
||||
traceExporter, err := otlptracegrpc.New(ctx)
|
||||
if err != nil {
|
||||
return shutdown, err
|
||||
}
|
||||
|
||||
tracerProvider := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithBatcher(traceExporter),
|
||||
sdktrace.WithResource(res),
|
||||
)
|
||||
otel.SetTracerProvider(tracerProvider)
|
||||
shutdownFns = append(shutdownFns, tracerProvider.Shutdown)
|
||||
|
||||
return shutdown, nil
|
||||
}
|
||||
Reference in New Issue
Block a user