diff --git a/config/logger.go b/config/logger.go new file mode 100644 index 0000000..7012069 --- /dev/null +++ b/config/logger.go @@ -0,0 +1,11 @@ +package config + +import "go.uber.org/zap" + +// NewLogger creates a new zap.Logger based on the provided configuration. +func NewLogger(config *Config) (*zap.Logger, error) { + if config.Development { + return zap.NewDevelopment() + } + return zap.NewProduction() +} diff --git a/go.mod b/go.mod index 60ff471..14e000a 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,8 @@ require ( github.com/gorilla/sessions v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/dig v1.19.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.26.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect ) require ( diff --git a/go.sum b/go.sum index b574d8f..4a8cd63 100644 --- a/go.sum +++ b/go.sum @@ -142,10 +142,15 @@ go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/internal/repository/sqlite.go b/internal/repository/sqlite.go index f5e5642..3ee84b9 100644 --- a/internal/repository/sqlite.go +++ b/internal/repository/sqlite.go @@ -14,6 +14,7 @@ import ( "github.com/google/uuid" _ "github.com/mattn/go-sqlite3" "go.uber.org/fx" + "go.uber.org/zap" ) var _ Repository = (*SQLite)(nil) @@ -21,11 +22,13 @@ var _ Repository = (*SQLite)(nil) type SQLite struct { conn *sql.DB config *config.Config + logger *zap.Logger } type SQLiteParams struct { fx.In Config *config.Config + Logger *zap.Logger } var ( @@ -54,6 +57,7 @@ func NewSQLite(p SQLiteParams) (Repository, error) { repo := &SQLite{ conn: db, config: p.Config, + logger: p.Logger, } err = repo.migrate(p.Config.Database.Schema) diff --git a/internal/repository/websocket.go b/internal/repository/websocket.go index b0e58ec..88e2157 100644 --- a/internal/repository/websocket.go +++ b/internal/repository/websocket.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "log" "net" "net/http" "time" @@ -15,16 +14,19 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" "go.uber.org/fx" + "go.uber.org/zap" ) var _ WebSocketRepository = (*WebSocket)(nil) type WebSocket struct { connections map[uuid.UUID][]*websocket.Conn + logger *zap.Logger } type WebSocketParams struct { fx.In + Logger *zap.Logger } var upgrader = websocket.Upgrader{ @@ -39,6 +41,7 @@ func NewWebSocket(p WebSocketParams) WebSocketRepository { connections := make(map[uuid.UUID][]*websocket.Conn) return &WebSocket{ connections: connections, + logger: p.Logger, } } @@ -64,7 +67,7 @@ func (ws *WebSocket) AddConnection(ctx context.Context, w http.ResponseWriter, r for { err := conn.SetReadDeadline(time.Now().Add(30 * time.Second)) if err != nil { - fmt.Println(err) + ws.logger.Error("error setting read deadline", zap.Error(err)) break } @@ -75,7 +78,7 @@ func (ws *WebSocket) AddConnection(ctx context.Context, w http.ResponseWriter, r if message.Type == "ping" { errWrite := conn.WriteJSON(types.WebSocketMessage{Type: "pong"}) if errWrite != nil { - fmt.Println(errWrite) + ws.logger.Error("error writing pong message", zap.Error(errWrite)) } } continue @@ -86,7 +89,7 @@ func (ws *WebSocket) AddConnection(ctx context.Context, w http.ResponseWriter, r break } - fmt.Println(err) + ws.logger.Error("error reading json message", zap.Error(err)) } conn.Close() ws.connections[retrospectiveID][i] = nil @@ -119,7 +122,7 @@ func (w *WebSocket) sendMessageToRetro(ctx context.Context, message types.WebSoc } err := conn.WriteJSON(message) if err != nil { - log.Printf("Error sending message %+v to connection: %v", message, err) + w.logger.Error("error sending message to connection", zap.Any("message", message), zap.Error(err)) } } @@ -170,11 +173,11 @@ func (w *WebSocket) DeleteQuestion(ctx context.Context, id uuid.UUID) (*types.Qu return nil, w.sendMessageToRetro(ctx, message, nil) } -func (s *WebSocket) GetOldRetrospectives(ctx context.Context, date time.Time) ([]uuid.UUID, error) { +func (*WebSocket) GetOldRetrospectives(ctx context.Context, date time.Time) ([]uuid.UUID, error) { panic("unimplemented") } -func (s *WebSocket) GetAllRetrospectives(ctx context.Context) ([]uuid.UUID, error) { +func (*WebSocket) GetAllRetrospectives(ctx context.Context) ([]uuid.UUID, error) { panic("unimplemented") } diff --git a/internal/schedule/schedule.go b/internal/schedule/schedule.go index ebd90d1..9f085e9 100644 --- a/internal/schedule/schedule.go +++ b/internal/schedule/schedule.go @@ -4,15 +4,16 @@ import ( "api/config" "api/internal/service" "context" - "log" "time" "go.uber.org/fx" + "go.uber.org/zap" ) type Schedule struct { service *service.Service config *config.Config + logger *zap.Logger stopCh chan struct{} } @@ -54,7 +55,7 @@ func (s *Schedule) start() { case <-ticker.C: s.cleanUp() case <-s.stopCh: - log.Println("stopping schedule") + s.logger.Info("stopping schedule") return } } @@ -66,9 +67,9 @@ func (s *Schedule) stop() { } func (s *Schedule) cleanUp() { - log.Println("starting clean up routine") + s.logger.Info("starting clean up routine") ctx := context.Background() if err := s.service.CleanUpRetros(ctx); err != nil { - log.Printf("error running clean up routine: %s", err.Error()) + s.logger.Error("error running clean up routine", zap.Error(err)) } } diff --git a/internal/server/server.go b/internal/server/server.go index 11d613a..94a1a36 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -8,7 +8,6 @@ import ( "context" "database/sql" "fmt" - "log" "net/http" "github.com/gin-contrib/sessions" @@ -18,6 +17,7 @@ import ( swaggerFiles "github.com/swaggo/files" // swagger embed files ginSwagger "github.com/swaggo/gin-swagger" // gin-swagger middleware "go.uber.org/fx" + "go.uber.org/zap" ) const ( @@ -29,6 +29,7 @@ type Controller struct { service *service.Service config *config.Config server *http.Server + logger *zap.Logger } type ControllerParams struct { @@ -36,12 +37,14 @@ type ControllerParams struct { Service *service.Service Config *config.Config Lifecycle fx.Lifecycle + Logger *zap.Logger } func New(p ControllerParams) *Controller { c := &Controller{ service: p.Service, config: p.Config, + logger: p.Logger, } p.Lifecycle.Append(fx.Hook{ @@ -57,10 +60,10 @@ func New(p ControllerParams) *Controller { return c } -// CORSMiddleware to handle CORS headers -func CORSMiddleware(conf *config.Config) gin.HandlerFunc { +// middlewareCORS to handle CORS headers +func (ct *Controller) middlewareCORS() gin.HandlerFunc { return func(c *gin.Context) { - c.Header("Access-Control-Allow-Origin", fmt.Sprintf("http://%s:5173", conf.Server.Host)) + c.Header("Access-Control-Allow-Origin", fmt.Sprintf("http://%s:5173", ct.config.Server.Host)) c.Header("Access-Control-Allow-Credentials", "true") c.Header( "Access-Control-Allow-Headers", @@ -77,9 +80,9 @@ func CORSMiddleware(conf *config.Config) gin.HandlerFunc { } } -// Authenticate middleware to check for retrospective_id cookie +// authenticate middleware to check for retrospective_id cookie // it sets the retrospective_id in the gin context if present -func Authenticate() gin.HandlerFunc { +func (ct *Controller) authenticate() gin.HandlerFunc { return func(c *gin.Context) { retroIDcookie, err := c.Cookie("retrospective_id") if err != nil { @@ -90,7 +93,7 @@ func Authenticate() gin.HandlerFunc { retroID, err := uuid.Parse(retroIDcookie) if err != nil { - log.Printf("error parsing retrospective_id: %s", err.Error()) + ct.logger.Error("error parsing retrospective_id", zap.Error(err)) c.JSON(http.StatusUnauthorized, gin.H{"error": "not in any retrospective"}) c.Abort() return @@ -100,10 +103,10 @@ func Authenticate() gin.HandlerFunc { } } -// EnsureSessionID middleware to ensure session ID exists +// ensureSessionID middleware to ensure session ID exists // it creates a new session ID if not present and saves it to the session store // the sessionID is stored under the key "session_id" in gin context. -func EnsureSessionID() gin.HandlerFunc { +func (ct *Controller) ensureSessionID() gin.HandlerFunc { return func(c *gin.Context) { session := sessions.Default(c) @@ -114,6 +117,7 @@ func EnsureSessionID() gin.HandlerFunc { if err := session.Save(); err != nil { // Fail hard: session must exist + ct.logger.Error("error while creating the session", zap.Error(err)) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ "error": "failed to create session", }) @@ -127,13 +131,13 @@ func EnsureSessionID() gin.HandlerFunc { } -func newSessionStore(conf *config.Config) gin.HandlerFunc { - store := cookie.NewStore([]byte(conf.SessionSecret)) +func (ct *Controller) newSessionStore() gin.HandlerFunc { + store := cookie.NewStore([]byte(ct.config.SessionSecret)) store.Options(sessions.Options{ Path: "/", MaxAge: 86400 * 7, HttpOnly: true, - Secure: !conf.Development, // true in prod with HTTPS + Secure: !ct.config.Development, // true in prod with HTTPS }) return sessions.Sessions(sessionName, store) @@ -150,7 +154,7 @@ func newSessionStore(conf *config.Config) gin.HandlerFunc { func (ct *Controller) health(c *gin.Context) { health, err := getServiceHealth() if err != nil { - log.Printf("error getting service health: %s", err.Error()) + ct.logger.Error("error getting service health", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "error getting service health"}) return } @@ -172,13 +176,13 @@ func (ct *Controller) health(c *gin.Context) { func (ct *Controller) createRetrospective(c *gin.Context) { var input types.RetrospectiveCreateRequest if err := c.BindJSON(&input); err != nil { - log.Printf("error parsing body content: %s", err.Error()) + ct.logger.Error("error parsing body content", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body content"}) return } if err := input.ValidateCreate(); err != nil { - log.Printf("invalid input: %s", err.Error()) + ct.logger.Error("invalid input", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -191,7 +195,7 @@ func (ct *Controller) createRetrospective(c *gin.Context) { err := ct.service.CreateRetrospective(c, &retrospective) if err != nil { - log.Printf("error creating retrospective: %s", err.Error()) + ct.logger.Error("error creating retrospective", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -214,20 +218,20 @@ func (ct *Controller) getRetrospective(c *gin.Context) { input := c.Param("id") id, err := uuid.Parse(input) if err != nil { - log.Printf("error parsing path ID: %s", err.Error()) + ct.logger.Error("error parsing path ID", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } retro, err := ct.service.GetRetrospective(c, id) if err == sql.ErrNoRows { - log.Printf("retrospective ID %s not found", id.String()) + ct.logger.Error("retrospective not found", zap.String("id", id.String())) c.JSON(http.StatusNotFound, gin.H{"error": "restrospective not found"}) return } if err != nil { - log.Printf("error getting retrospective: %s", err.Error()) + ct.logger.Error("error getting retrospective", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -253,20 +257,20 @@ func (ct *Controller) updateRetrospective(c *gin.Context) { input := c.Param("id") id, err := uuid.Parse(input) if err != nil { - log.Printf("error parsing path ID: %s", err.Error()) + ct.logger.Error("error parsing path ID", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } var inputRetro types.RetrospectiveCreateRequest if err := c.BindJSON(&inputRetro); err != nil { - log.Printf("error parsing body content: %s", err.Error()) + ct.logger.Error("error parsing body content", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body content"}) return } if err := inputRetro.ValidateUpdate(); err != nil { - log.Printf("invalid input: %s", err.Error()) + ct.logger.Error("invalid input", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -281,13 +285,13 @@ func (ct *Controller) updateRetrospective(c *gin.Context) { err = ct.service.UpdateRetrospective(c, retro) if err == sql.ErrNoRows { - log.Printf("retrospective ID %s not found", id.String()) + ct.logger.Error("retrospective not found", zap.String("id", id.String())) c.JSON(http.StatusNotFound, gin.H{"error": "restrospective not found"}) return } if err != nil { - log.Printf("error updating retrospective: %s", err.Error()) + ct.logger.Error("error updating retrospective", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -310,20 +314,20 @@ func (ct *Controller) deleteRetrospective(c *gin.Context) { input := c.Param("id") id, err := uuid.Parse(input) if err != nil { - log.Printf("error parsing path ID: %s", err.Error()) + ct.logger.Error("error parsing path ID", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } retro, err := ct.service.DeleteRetrospective(c, id) if err == sql.ErrNoRows { - log.Printf("retrospective ID %s not found", id.String()) + ct.logger.Error("retrospective not found", zap.String("id", id.String())) c.JSON(http.StatusNotFound, gin.H{"error": "restrospective not found"}) return } if err != nil { - log.Printf("error deleting retrospective: %s", err.Error()) + ct.logger.Error("error deleting retrospective", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } @@ -344,13 +348,13 @@ func (ct *Controller) deleteRetrospective(c *gin.Context) { func (ct *Controller) createQuestion(c *gin.Context) { var input types.QuestionCreateRequest if err := c.BindJSON(&input); err != nil { - log.Printf("error parsing body content: %s", err.Error()) + ct.logger.Error("error parsing body content", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body content"}) return } if err := input.ValidateCreate(); err != nil { - log.Printf("invalid input: %s", err.Error()) + ct.logger.Error("invalid input", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -363,11 +367,11 @@ func (ct *Controller) createQuestion(c *gin.Context) { err := ct.service.CreateQuestion(c, question) if err != nil { if err.Error() == "FOREIGN KEY constraint failed" { - log.Printf("error creating question: %s", err.Error()) + ct.logger.Error("error creating question", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "retrospective doesn't exist"}) return } - log.Printf("error creating question: %s", err.Error()) + ct.logger.Error("error creating question", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -391,20 +395,20 @@ func (ct *Controller) updateQuestion(c *gin.Context) { input := c.Param("id") id, err := uuid.Parse(input) if err != nil { - log.Printf("error parsing path ID: %s", err.Error()) + ct.logger.Error("error parsing path ID", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } var inputQuestion types.QuestionCreateRequest if err := c.BindJSON(&inputQuestion); err != nil { - log.Printf("error parsing body content: %s", err.Error()) + ct.logger.Error("error parsing body content", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body content"}) return } if err := inputQuestion.ValidateCreate(); err != nil { - log.Printf("invalid input: %s", err.Error()) + ct.logger.Error("invalid input", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -418,13 +422,13 @@ func (ct *Controller) updateQuestion(c *gin.Context) { err = ct.service.UpdateQuestion(c, question) if err == sql.ErrNoRows { - log.Printf("question ID %s not found", id.String()) + ct.logger.Error("question not found", zap.String("id", id.String())) c.JSON(http.StatusNotFound, gin.H{"error": "question not found"}) return } if err != nil { - log.Printf("error updating question: %s", err.Error()) + ct.logger.Error("error updating question", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -447,20 +451,20 @@ func (ct *Controller) deleteQuestion(c *gin.Context) { input := c.Param("id") id, err := uuid.Parse(input) if err != nil { - log.Printf("error parsing path ID: %s", err.Error()) + ct.logger.Error("error parsing path ID", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } question, err := ct.service.DeleteQuestion(c, id) if err == sql.ErrNoRows { - log.Printf("question ID %s not found", id.String()) + ct.logger.Error("question not found", zap.String("id", id.String())) c.JSON(http.StatusNotFound, gin.H{"error": "question not found"}) return } if err != nil { - log.Printf("error deleting question: %s", err.Error()) + ct.logger.Error("error deleting question", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } @@ -487,7 +491,7 @@ func (ct *Controller) subscribeChanges(c *gin.Context) { retroID, err := uuid.Parse(retroIDparam) if err != nil { - log.Printf("error parsing retrospective_id: %s", err.Error()) + ct.logger.Error("error parsing retrospective_id", zap.Error(err)) c.JSON(http.StatusUnauthorized, gin.H{"error": "not in any retrospective"}) return } @@ -495,9 +499,8 @@ func (ct *Controller) subscribeChanges(c *gin.Context) { err = ct.service.SubscribeChanges(c, c.Writer, c.Request) if err != nil { - errMessage := fmt.Errorf("error subscribing: %s", err.Error()) - log.Println(errMessage) - c.JSON(http.StatusBadRequest, gin.H{"error": errMessage}) + ct.logger.Error("error subscribing", zap.Error(err)) + c.JSON(http.StatusBadRequest, gin.H{"error": "error subscribing"}) return } @@ -518,13 +521,13 @@ func (ct *Controller) subscribeChanges(c *gin.Context) { func (ct *Controller) createAnswer(c *gin.Context) { var input *types.AnswerCreateRequest if err := c.BindJSON(&input); err != nil { - log.Printf("error parsing body content: %s", err.Error()) + ct.logger.Error("error parsing body content", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body content"}) return } if err := input.ValidateCreate(); err != nil { - log.Printf("invalid input: %s", err.Error()) + ct.logger.Error("invalid input", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -536,7 +539,7 @@ func (ct *Controller) createAnswer(c *gin.Context) { err := ct.service.CreateAnswer(c, answer) if err != nil { - log.Printf("error creating answer: %s", err.Error()) + ct.logger.Error("error creating answer", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -560,20 +563,20 @@ func (ct *Controller) updateAnswer(c *gin.Context) { input := c.Param("id") id, err := uuid.Parse(input) if err != nil { - log.Printf("error parsing path question ID: %s", err.Error()) + ct.logger.Error("error parsing path question ID", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question id"}) return } var inputAnswer *types.AnswerCreateRequest if err := c.BindJSON(&inputAnswer); err != nil { - log.Printf("error parsing body content: %s", err.Error()) + ct.logger.Error("error parsing body content", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body content"}) return } if err := inputAnswer.ValidateCreate(); err != nil { - log.Printf("invalid input: %s", err.Error()) + ct.logger.Error("invalid input", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -586,13 +589,13 @@ func (ct *Controller) updateAnswer(c *gin.Context) { err = ct.service.UpdateAnswer(c, answer) if err == sql.ErrNoRows { - log.Printf("answer ID %s not found", id.String()) + ct.logger.Error("answer not found", zap.String("id", id.String())) c.JSON(http.StatusNotFound, gin.H{"error": "answer not found"}) return } if err != nil { - log.Printf("error updating answer: %s", err.Error()) + ct.logger.Error("error updating answer", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -615,7 +618,7 @@ func (ct *Controller) deleteAnswer(c *gin.Context) { input := c.Param("id") id, err := uuid.Parse(input) if err != nil { - log.Printf("error parsing path question ID: %s", err.Error()) + ct.logger.Error("error parsing path question ID", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question id"}) return } @@ -625,13 +628,13 @@ func (ct *Controller) deleteAnswer(c *gin.Context) { } err = ct.service.DeleteAnswer(c, answer) if err == sql.ErrNoRows { - log.Printf("answer ID %s not found", id.String()) + ct.logger.Error("answer not found", zap.String("id", id.String())) c.JSON(http.StatusNotFound, gin.H{"error": "answer not found"}) return } if err != nil { - log.Printf("error deleting answer: %s", err.Error()) + ct.logger.Error("error deleting answer", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } @@ -668,13 +671,13 @@ func (ct *Controller) getLimits(c *gin.Context) { func (ct *Controller) voteAnswer(c *gin.Context) { var input types.AnswerVoteRequest if err := c.BindJSON(&input); err != nil { - log.Printf("error parsing body content: %s", err.Error()) + ct.logger.Error("error parsing body content", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body content"}) return } if err := input.Validate(); err != nil { - log.Printf("invalid input: %s", err.Error()) + ct.logger.Error("invalid input", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -690,7 +693,7 @@ func (ct *Controller) voteAnswer(c *gin.Context) { return } - log.Printf("error voting answer: %s", err.Error()) + ct.logger.Error("error voting answer", zap.Error(err)) switch err { case service.ErrVoteAlreadyExists: @@ -720,15 +723,15 @@ func (ct *Controller) Start() { router := gin.Default() if conf.Server.WithCors { - router.Use(CORSMiddleware(conf)) + router.Use(ct.middlewareCORS()) } if conf.Development { router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) } - router.Use(newSessionStore(conf)) - router.Use(EnsureSessionID()) + router.Use(ct.newSessionStore()) + router.Use(ct.ensureSessionID()) api := router.Group("/api") api.GET("/health", ct.health) @@ -740,7 +743,7 @@ func (ct *Controller) Start() { api.GET("/limits", ct.getLimits) authorized := api.Group("/") - authorized.Use(Authenticate()) + authorized.Use(ct.authenticate()) authorized.POST("/question", ct.createQuestion) authorized.PATCH("/question/:id", ct.updateQuestion) authorized.DELETE("/question/:id", ct.deleteQuestion) @@ -756,15 +759,15 @@ func (ct *Controller) Start() { Handler: router, } - log.Printf("starting server on %s", addr) + ct.logger.Info("starting server", zap.String("addr", addr)) if err := ct.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("error starting server: %s", err.Error()) + ct.logger.Fatal("error starting server", zap.Error(err)) } } func (ct *Controller) stop(ctx context.Context) error { if ct.server != nil { - log.Println("shutting down server...") + ct.logger.Info("shutting down server...") return ct.server.Shutdown(ctx) } return nil diff --git a/internal/service/service.go b/internal/service/service.go index 289743a..ebb75a5 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" - "log" "net/http" "time" "go.uber.org/fx" + "go.uber.org/zap" "api/config" "api/internal/repository" @@ -26,6 +26,7 @@ type Service struct { repository repository.Repository webSocketRepository repository.WebSocketRepository config *config.Config + logger *zap.Logger } type ServiceParams struct { @@ -33,6 +34,7 @@ type ServiceParams struct { Repository repository.Repository WebSocketRepository repository.WebSocketRepository Config *config.Config + Logger *zap.Logger } func New(p ServiceParams) *Service { @@ -40,6 +42,7 @@ func New(p ServiceParams) *Service { repository: p.Repository, webSocketRepository: p.WebSocketRepository, config: p.Config, + logger: p.Logger, } } @@ -222,6 +225,6 @@ func (s *Service) CleanUpRetros(ctx context.Context) error { } } - log.Printf("deleted %d retrospectives older than %s", len(ids), date.String()) + s.logger.Info("deleted retrospectives", zap.Int("count", len(ids)), zap.String("before", date.String())) return nil } diff --git a/main.go b/main.go index cfb3ef6..75a6f8c 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "api/internal/service" "go.uber.org/fx" + "go.uber.org/zap" ) func main() { @@ -24,6 +25,7 @@ func main() { } }, config.NewConfig, + config.NewLogger, repository.NewSQLite, repository.NewWebSocket, service.New, @@ -37,6 +39,16 @@ func main() { } return nil }, + + // Ensure logger is synced on shutdown + func(logger *zap.Logger, lc fx.Lifecycle) { + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return logger.Sync() + }, + }) + }, + server.New, schedule.New, ),