* Add tools for creating, retrieving, updating, and deleting thoughts. * Implement project management tools for creating and listing projects. * Introduce linking functionality between thoughts. * Add search and recall capabilities for thoughts based on semantic queries. * Implement statistics and summarization tools for thought analysis. * Create database migrations for thoughts, projects, and links. * Add helper functions for UUID parsing and project resolution.
111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
package observability
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type contextKey string
|
|
|
|
const requestIDContextKey contextKey = "request_id"
|
|
|
|
func Chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
|
|
for i := len(middlewares) - 1; i >= 0; i-- {
|
|
h = middlewares[i](h)
|
|
}
|
|
return h
|
|
}
|
|
|
|
func RequestID() func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
requestID := r.Header.Get("X-Request-Id")
|
|
if requestID == "" {
|
|
requestID = uuid.NewString()
|
|
}
|
|
w.Header().Set("X-Request-Id", requestID)
|
|
ctx := context.WithValue(r.Context(), requestIDContextKey, requestID)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|
|
|
|
func Recover(log *slog.Logger) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
log.Error("panic recovered",
|
|
slog.Any("panic", recovered),
|
|
slog.String("request_id", RequestIDFromContext(r.Context())),
|
|
slog.String("stack", string(debug.Stack())),
|
|
)
|
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
}
|
|
}()
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func AccessLog(log *slog.Logger) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
recorder := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
|
|
started := time.Now()
|
|
next.ServeHTTP(recorder, r)
|
|
|
|
log.Info("http request",
|
|
slog.String("request_id", RequestIDFromContext(r.Context())),
|
|
slog.String("method", r.Method),
|
|
slog.String("path", r.URL.Path),
|
|
slog.Int("status", recorder.status),
|
|
slog.Duration("duration", time.Since(started)),
|
|
slog.String("remote_addr", stripPort(r.RemoteAddr)),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Timeout(timeout time.Duration) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
if timeout <= 0 {
|
|
return next
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), timeout)
|
|
defer cancel()
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|
|
|
|
func RequestIDFromContext(ctx context.Context) string {
|
|
value, _ := ctx.Value(requestIDContextKey).(string)
|
|
return value
|
|
}
|
|
|
|
type statusRecorder struct {
|
|
http.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
func (s *statusRecorder) WriteHeader(statusCode int) {
|
|
s.status = statusCode
|
|
s.ResponseWriter.WriteHeader(statusCode)
|
|
}
|
|
|
|
func stripPort(remote string) string {
|
|
host, _, err := net.SplitHostPort(remote)
|
|
if err != nil {
|
|
return remote
|
|
}
|
|
return host
|
|
}
|