mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-28 13:56:21 +02:00
Release prep: 54 engines, self-hosted signatures, i18n, dashboard updates
This commit is contained in:
parent
694e32be26
commit
41cbfd6e0a
178 changed files with 36008 additions and 399 deletions
57
internal/infrastructure/logging/logger.go
Normal file
57
internal/infrastructure/logging/logger.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Package logging provides structured logging via Go's log/slog.
|
||||
// Production: JSON output. Development: text output with colors.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// logger := logging.New("json", "info") // production
|
||||
// logger := logging.New("text", "debug") // development
|
||||
// logger.Info("event ingested", "event_id", id, "source", src)
|
||||
package logging
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// New creates a structured logger.
|
||||
// format: "json" (production) or "text" (development).
|
||||
// level: "debug", "info", "warn", "error".
|
||||
func New(format, level string) *slog.Logger {
|
||||
return NewWithOutput(format, level, os.Stdout)
|
||||
}
|
||||
|
||||
// NewWithOutput creates a logger writing to the given writer.
|
||||
func NewWithOutput(format, level string, w io.Writer) *slog.Logger {
|
||||
lvl := parseLevel(level)
|
||||
opts := &slog.HandlerOptions{Level: lvl}
|
||||
|
||||
var handler slog.Handler
|
||||
switch strings.ToLower(format) {
|
||||
case "json":
|
||||
handler = slog.NewJSONHandler(w, opts)
|
||||
default:
|
||||
handler = slog.NewTextHandler(w, opts)
|
||||
}
|
||||
|
||||
return slog.New(handler)
|
||||
}
|
||||
|
||||
// WithComponent returns a logger with a "component" attribute.
|
||||
func WithComponent(logger *slog.Logger, component string) *slog.Logger {
|
||||
return logger.With("component", component)
|
||||
}
|
||||
|
||||
func parseLevel(s string) slog.Level {
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
return slog.LevelDebug
|
||||
case "warn", "warning":
|
||||
return slog.LevelWarn
|
||||
case "error":
|
||||
return slog.LevelError
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
73
internal/infrastructure/logging/middleware.go
Normal file
73
internal/infrastructure/logging/middleware.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const requestIDKey contextKey = "request_id"
|
||||
|
||||
// RequestID generates a short unique request ID.
|
||||
func RequestID() string {
|
||||
b := make([]byte, 8)
|
||||
rand.Read(b)
|
||||
return fmt.Sprintf("%x", b)
|
||||
}
|
||||
|
||||
// WithRequestID returns a context with a request ID attached.
|
||||
func WithRequestID(ctx context.Context, id string) context.Context {
|
||||
return context.WithValue(ctx, requestIDKey, id)
|
||||
}
|
||||
|
||||
// GetRequestID extracts the request ID from context (empty if not set).
|
||||
func GetRequestID(ctx context.Context) string {
|
||||
if id, ok := ctx.Value(requestIDKey).(string); ok {
|
||||
return id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RequestIDMiddleware injects a unique request ID into each request context
|
||||
// and logs request start/end with duration.
|
||||
func RequestIDMiddleware(logger *slog.Logger, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
reqID := r.Header.Get("X-Request-ID")
|
||||
if reqID == "" {
|
||||
reqID = RequestID()
|
||||
}
|
||||
w.Header().Set("X-Request-ID", reqID)
|
||||
|
||||
ctx := WithRequestID(r.Context(), reqID)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
start := time.Now()
|
||||
wrapped := &statusWriter{ResponseWriter: w, status: 200}
|
||||
next.ServeHTTP(wrapped, r)
|
||||
dur := time.Since(start)
|
||||
|
||||
logger.Info("http_request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"status", wrapped.status,
|
||||
"duration_ms", dur.Milliseconds(),
|
||||
"request_id", reqID,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// statusWriter wraps ResponseWriter to capture status code.
|
||||
type statusWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (w *statusWriter) WriteHeader(code int) {
|
||||
w.status = code
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue