Files
tiennm99 daeaf0c605 chore: drop CF→AWS migration tooling and stale JS-port references
The CF→AWS data migration (closed 2026-05-16) is long done and the
tooling isn't wired into any production path. Remove the one-shot binary,
its support package, and the migration runbook.

In live code, replace 'JS-parity' / 'same shape as JS' / 'cross-runtime
KV migration' comments with the real, stable reason for each behavior
(wire-format invariant, null-vs-zero distinction, CloudWatch alarm field
name, etc.). 24 files touched across lolschedule, loldle, wordle, twentyq,
trading, misc, util, server, metrics, ai, keylock.

- delete cmd/migrate_cf_data/
- delete internal/migration/
- delete docs/cf-to-aws-migration-runbook.md
2026-05-25 09:39:17 +07:00

79 lines
2.5 KiB
Go

package server
import (
"net/http"
"runtime/debug"
"time"
"github.com/tiennm99/miti99bot/internal/log"
)
// statusRecorder wraps http.ResponseWriter to capture the final status
// code. http.ResponseWriter doesn't expose what was written; the middleware
// needs the status to log a per-request `req` line.
type statusRecorder struct {
http.ResponseWriter
status int
}
func (r *statusRecorder) WriteHeader(code int) {
r.status = code
r.ResponseWriter.WriteHeader(code)
}
// status returns the recorded status code, defaulting to 200 when no
// explicit WriteHeader was called (Go's net/http implicitly writes 200 on
// the first body write).
func (r *statusRecorder) effectiveStatus() int {
if r.status == 0 {
return http.StatusOK
}
return r.status
}
// LogRequests wraps an http.Handler with a request log line:
//
// {"msg":"req","method":"POST","path":"/webhook","status":200,"ms":12}
//
// CloudWatch Logs filters on `jsonPayload.msg=req AND jsonPayload.status>=500`
// for 5xx-rate alerting — keep the field names stable or the alarm goes dark.
//
// The req line is emitted from a deferred closure so a panic in a downstream
// handler still produces an observable log entry — without this, a cron
// panic would disappear silently (http.Server does its own recover but never
// runs middleware again on the way out).
func LogRequests(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rec := &statusRecorder{ResponseWriter: w}
defer func() {
rec.status = recoverPanicStatus(recover(), rec.status)
log.Info("req",
"method", r.Method,
"path", r.URL.Path,
"status", rec.effectiveStatus(),
"ms", time.Since(start).Milliseconds(),
)
}()
next.ServeHTTP(rec, r)
})
}
// recoverPanicStatus folds a recovered panic into the status to log: returns
// 500 if a panic was recovered (and re-panics nothing — http.Server will
// terminate the connection cleanly while the deferred req log still runs),
// otherwise returns the original status untouched.
//
// Re-panicking would lose the deferred log line in some recover-order edge
// cases; absorbing the panic here matches the webhook handler's posture of
// "log the failure, keep the goroutine clean".
func recoverPanicStatus(rec any, currentStatus int) int {
if rec == nil {
return currentStatus
}
log.Error("middleware recovered panic",
"panic", rec,
"stack", string(debug.Stack()))
return http.StatusInternalServerError
}