Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d3ce5baf8 |
+3
-34
@@ -1,14 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/config"
|
||||
giteaclient "gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/gitea"
|
||||
@@ -40,35 +36,8 @@ func main() {
|
||||
handler = middleware.Auth(cfg.SessionSecret, cfg.GiteaToken)(handler)
|
||||
handler = middleware.Logging()(handler)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.ListenAddr,
|
||||
Handler: handler,
|
||||
slog.Info("server starting", "addr", cfg.ListenAddr, "gitea_url", cfg.GiteaURL)
|
||||
if err := http.ListenAndServe(cfg.ListenAddr, handler); err != nil {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
|
||||
// Channel to receive shutdown signals.
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
// Start server in a goroutine.
|
||||
go func() {
|
||||
slog.Info("server starting", "addr", cfg.ListenAddr, "gitea_url", cfg.GiteaURL)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Block until a shutdown signal is received.
|
||||
sig := <-quit
|
||||
slog.Info("shutdown signal received, draining in-flight requests", "signal", sig.String())
|
||||
|
||||
// Give in-flight requests up to 15 seconds to complete.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
slog.Error("server forced to shutdown", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
slog.Info("server stopped gracefully")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -17,21 +19,39 @@ func (rw *responseWriter) WriteHeader(code int) {
|
||||
rw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Logging returns middleware that logs each HTTP request with structured logging.
|
||||
// generateRequestID creates a short random hex string for request tracing.
|
||||
func generateRequestID() string {
|
||||
b := make([]byte, 8)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// Logging returns middleware that logs each HTTP request with structured fields:
|
||||
// method, path, status, duration (ms), request-id, and remote address.
|
||||
func Logging() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
requestID := generateRequestID()
|
||||
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
||||
|
||||
// Set request ID header for downstream correlation.
|
||||
w.Header().Set("X-Request-ID", requestID)
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
|
||||
duration := time.Since(start)
|
||||
slog.Info("http request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"status", rw.statusCode,
|
||||
"duration", time.Since(start).String(),
|
||||
"duration_ms", duration.Milliseconds(),
|
||||
"duration", duration.String(),
|
||||
"request_id", requestID,
|
||||
"remote", r.RemoteAddr,
|
||||
"user_agent", r.UserAgent(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user