refactor: wire centralized job scheduler and custom request logging in main.go
Replace inline cleanup goroutines and default chi logger with centralized jobs scheduler and custom colored request logger. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
+84
-29
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"crussell/auth"
|
||||
"crussell/internal/dav"
|
||||
"crussell/internal/jobs"
|
||||
"crussell/internal/logutil"
|
||||
"crussell/internal/s3"
|
||||
"crussell/internal/square"
|
||||
"encoding/json"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -65,13 +68,35 @@ func limitBody(limit int64) func(http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
const (
|
||||
defaultBodyLimit int64 = 1 * 1024 * 1024 // 1MB
|
||||
uploadBodyLimit int64 = 15 * 1024 * 1024 // 15MB
|
||||
portfolioBodyLimit int64 = 40 * 1024 * 1024 // 40MB (7 variants from 20MB source)
|
||||
reservationCleanupInterval = 5 * time.Minute
|
||||
reservationCleanupTimeout = 30 * time.Second
|
||||
defaultBodyLimit int64 = 1 * 1024 * 1024 // 1MB
|
||||
uploadBodyLimit int64 = 15 * 1024 * 1024 // 15MB
|
||||
portfolioBodyLimit int64 = 40 * 1024 * 1024 // 40MB (7 variants from 20MB source)
|
||||
)
|
||||
|
||||
// nColor / bColor — Chi-style ANSI colors for request logging.
|
||||
type nColor string
|
||||
type bColor string
|
||||
|
||||
var (
|
||||
reset = nColor(logutil.Reset)
|
||||
nGreen = nColor(logutil.Green)
|
||||
nYellow = nColor(logutil.Yellow)
|
||||
nCyan = nColor(logutil.Cyan)
|
||||
nRed = nColor(logutil.Red)
|
||||
|
||||
bGreen = bColor(logutil.BoldGreen)
|
||||
bYellow = bColor(logutil.BoldYellow)
|
||||
bRed = bColor(logutil.BoldRed)
|
||||
bBlue = bColor(logutil.BoldBlue)
|
||||
bMagenta = bColor(logutil.BoldMagenta)
|
||||
|
||||
debugLvl = nColor(logutil.DebugLvl)
|
||||
warnLvl = nColor(logutil.WarnLvl)
|
||||
errorLvl = nColor(logutil.ErrorLvl)
|
||||
)
|
||||
|
||||
var apiLog = log.New(os.Stdout, "", log.LstdFlags)
|
||||
|
||||
func initDB() {
|
||||
if err := db.Connect(); err != nil {
|
||||
log.Fatal("Failed to connect to DB:", err)
|
||||
@@ -139,14 +164,63 @@ func main() {
|
||||
initDav()
|
||||
initS3()
|
||||
initSquare()
|
||||
auth.StartJTICleanup()
|
||||
|
||||
sched := jobs.New()
|
||||
jobs.RegisterAll(sched)
|
||||
sched.Start()
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
// --- Global Middleware ---
|
||||
r.Use(middleware.RequestID)
|
||||
// Custom RequestID using shared random prefix (matches jobs scheduler)
|
||||
var reqCounter atomic.Uint64
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
myid := reqCounter.Add(1)
|
||||
requestID := fmt.Sprintf("%s/%s-%06d", jobs.Hostname(), jobs.RandomPrefix(), myid)
|
||||
ctx := context.WithValue(r.Context(), middleware.RequestIDKey, requestID)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
})
|
||||
r.Use(middleware.ClientIPFromHeader("X-Real-IP"))
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
status := ww.Status()
|
||||
bytes := ww.BytesWritten()
|
||||
reqID := middleware.GetReqID(r.Context())
|
||||
|
||||
var level nColor
|
||||
var statusColor bColor
|
||||
switch {
|
||||
case status >= 500:
|
||||
level, statusColor = errorLvl, bRed
|
||||
case status >= 400:
|
||||
level, statusColor = warnLvl, bYellow
|
||||
default:
|
||||
level, statusColor = debugLvl, bGreen
|
||||
}
|
||||
|
||||
clientIP := middleware.GetClientIP(r.Context())
|
||||
if clientIP == "" {
|
||||
clientIP = r.RemoteAddr
|
||||
}
|
||||
apiLog.Printf("%s %s%s%s \"%s%s %s%s %s%s\" from %s - %s %s%03d%s %s%dB%s in %s",
|
||||
level,
|
||||
nYellow, reqID, reset,
|
||||
bMagenta, r.Method, nCyan, r.URL.String(), nCyan, r.Proto, reset,
|
||||
clientIP,
|
||||
statusColor, status, reset,
|
||||
bBlue, bytes, reset,
|
||||
logutil.ColoredDuration(time.Since(t1)),
|
||||
)
|
||||
}()
|
||||
next.ServeHTTP(ww, r)
|
||||
})
|
||||
})
|
||||
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(middleware.Timeout(15 * time.Second))
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
@@ -429,26 +503,6 @@ r.Route("/admin/users", func(r chi.Router) {
|
||||
// Webhooks (no auth - Square sends to base path)
|
||||
r.Post("/webhooks/square", webhooks.HandleSquareWebhook)
|
||||
|
||||
// Background cleanup of expired reservations
|
||||
cleanupCtx, cleanupStop := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
ticker := time.NewTicker(reservationCleanupInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), reservationCleanupTimeout)
|
||||
if err := scheduling.CleanupOldReservations(ctx); err != nil {
|
||||
log.Printf("reservation cleanup error: %v", err)
|
||||
}
|
||||
cancel()
|
||||
case <-cleanupCtx.Done():
|
||||
log.Println("reservation cleanup goroutine stopped")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: r,
|
||||
@@ -464,7 +518,8 @@ r.Route("/admin/users", func(r chi.Router) {
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Printf("Server forced to shutdown: %v", err)
|
||||
}
|
||||
cleanupStop()
|
||||
<-sched.Shutdown()
|
||||
log.Println("Background jobs stopped")
|
||||
}()
|
||||
|
||||
fmt.Println("Server is listening on :8080")
|
||||
|
||||
Reference in New Issue
Block a user