feat: add observability improvements (metrics, audit log, structured logging)
All checks were successful
Check / check (pull_request) Successful in 1m45s
All checks were successful
Check / check (pull_request) Successful in 1m45s
- Add Prometheus metrics package (internal/metrics) with deployment, container health, webhook, HTTP request, and audit counters/histograms - Add audit_log SQLite table via migration 007 - Add AuditEntry model with CRUD operations and query methods - Add audit service (internal/service/audit) for recording user actions - Instrument deploy service with deployment duration, count, and in-flight metrics; container health gauge updates on deploy completion - Instrument webhook service with event counters by app/type/matched - Instrument HTTP middleware with request count, duration, and response size metrics; also log response bytes in structured request logs - Add audit logging to all key handler operations: login/logout, app CRUD, deploy, cancel, rollback, restart/stop/start, webhook receipt, and initial setup - Add GET /api/audit endpoint for querying recent audit entries - Make /metrics endpoint always available (optionally auth-protected) - Add comprehensive tests for metrics, audit model, and audit service - Update existing test infrastructure with metrics and audit dependencies - Update README with Observability section documenting all metrics, audit log, and structured logging
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"sneak.berlin/go/upaas/internal/config"
|
||||
"sneak.berlin/go/upaas/internal/globals"
|
||||
"sneak.berlin/go/upaas/internal/logger"
|
||||
"sneak.berlin/go/upaas/internal/metrics"
|
||||
"sneak.berlin/go/upaas/internal/service/auth"
|
||||
)
|
||||
|
||||
@@ -35,33 +36,37 @@ type Params struct {
|
||||
Globals *globals.Globals
|
||||
Config *config.Config
|
||||
Auth *auth.Service
|
||||
Metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// Middleware provides HTTP middleware.
|
||||
type Middleware struct {
|
||||
log *slog.Logger
|
||||
params *Params
|
||||
log *slog.Logger
|
||||
metrics *metrics.Metrics
|
||||
params *Params
|
||||
}
|
||||
|
||||
// New creates a new Middleware instance.
|
||||
func New(_ fx.Lifecycle, params Params) (*Middleware, error) {
|
||||
return &Middleware{
|
||||
log: params.Logger.Get(),
|
||||
params: ¶ms,
|
||||
log: params.Logger.Get(),
|
||||
metrics: params.Metrics,
|
||||
params: ¶ms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// loggingResponseWriter wraps http.ResponseWriter to capture status code.
|
||||
// loggingResponseWriter wraps http.ResponseWriter to capture status code and bytes written.
|
||||
type loggingResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
statusCode int
|
||||
statusCode int
|
||||
bytesWritten int
|
||||
}
|
||||
|
||||
func newLoggingResponseWriter(
|
||||
writer http.ResponseWriter,
|
||||
) *loggingResponseWriter {
|
||||
return &loggingResponseWriter{writer, http.StatusOK}
|
||||
return &loggingResponseWriter{ResponseWriter: writer, statusCode: http.StatusOK}
|
||||
}
|
||||
|
||||
func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||
@@ -69,7 +74,14 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||
lrw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Logging returns a request logging middleware.
|
||||
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
|
||||
n, err := lrw.ResponseWriter.Write(b)
|
||||
lrw.bytesWritten += n
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Logging returns a request logging middleware that also records HTTP metrics.
|
||||
func (m *Middleware) Logging() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(
|
||||
@@ -83,6 +95,8 @@ func (m *Middleware) Logging() func(http.Handler) http.Handler {
|
||||
defer func() {
|
||||
latency := time.Since(start)
|
||||
reqID := middleware.GetReqID(ctx)
|
||||
statusStr := strconv.Itoa(lrw.statusCode)
|
||||
|
||||
m.log.InfoContext(ctx, "request",
|
||||
"request_start", start,
|
||||
"method", request.Method,
|
||||
@@ -93,8 +107,19 @@ func (m *Middleware) Logging() func(http.Handler) http.Handler {
|
||||
"proto", request.Proto,
|
||||
"remoteIP", realIP(request),
|
||||
"status", lrw.statusCode,
|
||||
"bytes", lrw.bytesWritten,
|
||||
"latency_ms", latency.Milliseconds(),
|
||||
)
|
||||
|
||||
m.metrics.HTTPRequestsTotal.WithLabelValues(
|
||||
request.Method, statusStr,
|
||||
).Inc()
|
||||
m.metrics.HTTPRequestDuration.WithLabelValues(
|
||||
request.Method,
|
||||
).Observe(latency.Seconds())
|
||||
m.metrics.HTTPResponseSizeBytes.WithLabelValues(
|
||||
request.Method,
|
||||
).Observe(float64(lrw.bytesWritten))
|
||||
}()
|
||||
|
||||
next.ServeHTTP(lrw, request)
|
||||
|
||||
Reference in New Issue
Block a user