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"
|
"context"
|
||||||
"crussell/auth"
|
"crussell/auth"
|
||||||
"crussell/internal/dav"
|
"crussell/internal/dav"
|
||||||
|
"crussell/internal/jobs"
|
||||||
|
"crussell/internal/logutil"
|
||||||
"crussell/internal/s3"
|
"crussell/internal/s3"
|
||||||
"crussell/internal/square"
|
"crussell/internal/square"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -13,6 +15,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -65,13 +68,35 @@ func limitBody(limit int64) func(http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultBodyLimit int64 = 1 * 1024 * 1024 // 1MB
|
defaultBodyLimit int64 = 1 * 1024 * 1024 // 1MB
|
||||||
uploadBodyLimit int64 = 15 * 1024 * 1024 // 15MB
|
uploadBodyLimit int64 = 15 * 1024 * 1024 // 15MB
|
||||||
portfolioBodyLimit int64 = 40 * 1024 * 1024 // 40MB (7 variants from 20MB source)
|
portfolioBodyLimit int64 = 40 * 1024 * 1024 // 40MB (7 variants from 20MB source)
|
||||||
reservationCleanupInterval = 5 * time.Minute
|
|
||||||
reservationCleanupTimeout = 30 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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() {
|
func initDB() {
|
||||||
if err := db.Connect(); err != nil {
|
if err := db.Connect(); err != nil {
|
||||||
log.Fatal("Failed to connect to DB:", err)
|
log.Fatal("Failed to connect to DB:", err)
|
||||||
@@ -139,14 +164,63 @@ func main() {
|
|||||||
initDav()
|
initDav()
|
||||||
initS3()
|
initS3()
|
||||||
initSquare()
|
initSquare()
|
||||||
auth.StartJTICleanup()
|
|
||||||
|
sched := jobs.New()
|
||||||
|
jobs.RegisterAll(sched)
|
||||||
|
sched.Start()
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
// --- Global Middleware ---
|
// --- 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.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.Recoverer)
|
||||||
r.Use(middleware.Timeout(15 * time.Second))
|
r.Use(middleware.Timeout(15 * time.Second))
|
||||||
r.Use(func(next http.Handler) http.Handler {
|
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)
|
// Webhooks (no auth - Square sends to base path)
|
||||||
r.Post("/webhooks/square", webhooks.HandleSquareWebhook)
|
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{
|
srv := &http.Server{
|
||||||
Addr: ":8080",
|
Addr: ":8080",
|
||||||
Handler: r,
|
Handler: r,
|
||||||
@@ -464,7 +518,8 @@ r.Route("/admin/users", func(r chi.Router) {
|
|||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
log.Printf("Server forced to shutdown: %v", err)
|
log.Printf("Server forced to shutdown: %v", err)
|
||||||
}
|
}
|
||||||
cleanupStop()
|
<-sched.Shutdown()
|
||||||
|
log.Println("Background jobs stopped")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fmt.Println("Server is listening on :8080")
|
fmt.Println("Server is listening on :8080")
|
||||||
|
|||||||
Reference in New Issue
Block a user