Add structured logging and centralized error handling
- Initialize slog in the serve command with terminal/file support - Introduce `AppError` with HTTP status for unified service-layer errors - Replace ad-hoc `api.Error` calls with `api.RespondError` - Wrap internal errors with `model.NewInternalError` and add reference IDs - Add `RequestID` middleware and switch router from `gin.Default` to `gin.New`
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/dhao2001/mygo/internal/model"
|
||||
)
|
||||
|
||||
// ErrorResponse is the standard JSON body for HTTP API errors.
|
||||
@@ -22,3 +31,42 @@ func Error(c *gin.Context, status int, message string) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// RespondError unpacks an error from the service layer and writes a JSON
|
||||
// error response. It expects *model.AppError; unexpected non-AppError
|
||||
// values are treated as 500 with a safe message. For 500+ errors, a
|
||||
// random reference hash is included in the response so operators can
|
||||
// correlate it with server logs.
|
||||
func RespondError(c *gin.Context, err error) {
|
||||
var ae *model.AppError
|
||||
if !errors.As(err, &ae) {
|
||||
ref := randomHex(8)
|
||||
slog.ErrorContext(c.Request.Context(), "non-AppError escaped to handler",
|
||||
"ref", ref, "error", err, "type", fmt.Sprintf("%T", err))
|
||||
Error(c, http.StatusInternalServerError, "internal server error (ref: "+ref+")")
|
||||
return
|
||||
}
|
||||
|
||||
// 500+ errors: log full detail with a reference hash, return sanitized message.
|
||||
if ae.Status >= 500 {
|
||||
ref := randomHex(8)
|
||||
slog.ErrorContext(c.Request.Context(), "internal server error",
|
||||
"ref", ref, slog.Any("error", ae.Err), "message", ae.Message)
|
||||
Error(c, ae.Status, "internal server error (ref: "+ref+")")
|
||||
return
|
||||
}
|
||||
|
||||
// 4xx errors with internal cause: log at WARN for diagnostics.
|
||||
if ae.Err != nil {
|
||||
slog.WarnContext(c.Request.Context(), ae.Message,
|
||||
"status", ae.Status, slog.Any("error", ae.Err))
|
||||
}
|
||||
|
||||
Error(c, ae.Status, ae.Message)
|
||||
}
|
||||
|
||||
func randomHex(n int) string {
|
||||
b := make([]byte, n)
|
||||
rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user