From 4e8d571cb47d04de8521e35c2cffb5e470c53969 Mon Sep 17 00:00:00 2001 From: Tomas Oh Date: Sun, 12 Apr 2026 14:31:56 -0500 Subject: [PATCH 1/3] introduce new logging mechanism wrapping log/slog --- internal/api/logging/logger.go | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 internal/api/logging/logger.go diff --git a/internal/api/logging/logger.go b/internal/api/logging/logger.go new file mode 100644 index 00000000..168c8b61 --- /dev/null +++ b/internal/api/logging/logger.go @@ -0,0 +1,43 @@ +package logging + +import ( + "fmt" + "log/slog" + "os" + "time" + + "github.com/gin-gonic/gin" +) + +// NewLogger returns the default server logger. +func NewLogger() *slog.Logger { + opts := &slog.HandlerOptions{AddSource: true} + return slog.New(slog.NewJSONHandler(os.Stdout, opts)).With("component", "SERVER") +} + +// Fatal logs a message at Error level then exits with code 1. +func Fatal(logger *slog.Logger, msg string, args ...any) { + logger.Error(msg, args...) + os.Exit(1) +} + +// Fatal logs a formatted message at Error level then exits with code 1. +func Fatalf(logger *slog.Logger, format string, args ...any) { + logger.Error("fatal", "msg", fmt.Sprintf(format, args...)) + os.Exit(1) +} + +// RequestLogger returns a gin middleware that logs each request using the provided logger. +func RequestLogger(logger *slog.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + c.Next() + logger.Info("request", + "method", c.Request.Method, + "path", c.Request.URL.Path, + "status", c.Writer.Status(), + "latency_ms", time.Since(start).Milliseconds(), + "client_ip", c.ClientIP(), + ) + } +} From 0e5436e9647c3b887416c49c3d4e2ce3162196cd Mon Sep 17 00:00:00 2001 From: Tomas Oh Date: Sun, 12 Apr 2026 14:46:27 -0500 Subject: [PATCH 2/3] include logging module at api level --- cmd/acmcsuf-api/main.go | 9 +++++---- internal/api/server.go | 21 +++++++++++---------- internal/api/services/announcement.go | 15 ++------------- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/cmd/acmcsuf-api/main.go b/cmd/acmcsuf-api/main.go index dfae60cb..afca65bd 100644 --- a/cmd/acmcsuf-api/main.go +++ b/cmd/acmcsuf-api/main.go @@ -4,12 +4,12 @@ import ( "context" "flag" "fmt" - "log" "os" "os/signal" "syscall" "github.com/acmcsufoss/api.acmcsuf.com/internal/api" + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/logging" ) var Version = "dev" @@ -24,8 +24,9 @@ func main() { os.Exit(0) } + logger := logging.NewLogger() + // =================== Goroutine management =================== - log.SetPrefix("[SERVER] ") ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -33,11 +34,11 @@ func main() { signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) go func() { <-signalChan - log.Println("\x1b[32mShutting down the server...\x1b[0m") + logger.Info("\x1b[32mShutting down the server...\x1b[0m") // when cancel is called, it sends a "done" signal to ctx cancel() }() // =================== Start the server =================== - api.Run(ctx) + api.Run(ctx, logger) } diff --git a/internal/api/server.go b/internal/api/server.go index 4fdb21b4..da4be8b8 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -6,7 +6,7 @@ import ( "context" "database/sql" "fmt" - "log" + "log/slog" "github.com/gin-gonic/gin" "github.com/golang-migrate/migrate/v4" @@ -16,6 +16,7 @@ import ( "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/logging" mw "github.com/acmcsufoss/api.acmcsuf.com/internal/api/middleware" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/routes" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" @@ -23,19 +24,19 @@ import ( // Run initializes the database, services, and router, then starts the server. // It waits for the context to be canceled to initiate a graceful shutdown. -func Run(ctx context.Context) { +func Run(ctx context.Context, logger *slog.Logger) { cfg := config.Load() db, closer, err := NewDB(ctx, cfg.DatabaseURL) if err != nil { - log.Fatal(err) + logging.Fatal(logger, "could not open database", "error", err) } defer closer() // Apply db migrations driver, err := sqlite3.WithInstance(db, &sqlite3.Config{}) if err != nil { - log.Fatalf("could not create sqlite3 driver: %v\n", err) + logging.Fatal(logger, "could not create sqlite3 driver", "error", err) } m, err := migrate.NewWithDatabaseInstance( "file://sql/migrations", @@ -43,10 +44,10 @@ func Run(ctx context.Context) { driver, ) if err != nil { - log.Fatalf("could not create migration instance: %v\n", err) + logging.Fatal(logger, "could not create migration instance", "error", err) } if err := m.Up(); err != nil && err != migrate.ErrNoChange { - log.Fatalf("could not run db migrations: %v\n", err) + logging.Fatal(logger, "could not run db migrations", "error", err) } // Now we init services & gin router, and then start the server @@ -54,8 +55,8 @@ func Run(ctx context.Context) { eventsService := services.NewEventsService(queries) announcementService := services.NewAnnouncementService(queries) boardService := services.NewBoardService(queries, db) - router := gin.Default() - router.Use(mw.Cors(), mw.Ratelimiter()) + router := gin.New() + router.Use(logging.RequestLogger(logger), gin.Recovery(), mw.Cors(), mw.Ratelimiter()) router.SetTrustedProxies(cfg.TrustedProxies) routes.SetupRoot(router) @@ -69,14 +70,14 @@ func Run(ctx context.Context) { } if err := router.Run(serverAddr); err != nil { - log.Fatalf("Failed to start server: %v", err) + logging.Fatal(logger, "failed to start server", "error", err) } }() // This is a blocking call that prevents the function from finishing until the signal // is received. <-ctx.Done() - log.Println("\x1b[32mServer shut down.\x1b[0m") + logger.Info("server shut down") } func NewDB(ctx context.Context, url string) (*sql.DB, func(), error) { diff --git a/internal/api/services/announcement.go b/internal/api/services/announcement.go index 818e06c2..208413e1 100644 --- a/internal/api/services/announcement.go +++ b/internal/api/services/announcement.go @@ -4,7 +4,6 @@ import ( "context" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" - "log" ) type AnnouncementServicer interface { @@ -64,19 +63,9 @@ func (s *AnnouncementService) List(ctx context.Context, func (s *AnnouncementService) Update(ctx context.Context, uuid string, params dbmodels.UpdateAnnouncementParams) error { - err := s.q.UpdateAnnouncement(ctx, params) - if err != nil { - log.Printf("Error updating announcement with UUID %s: %v", uuid, err) - return err - } - return nil + return s.q.UpdateAnnouncement(ctx, params) } func (s *AnnouncementService) Delete(ctx context.Context, uuid string) error { - err := s.q.DeleteAnnouncement(ctx, uuid) - if err != nil { - log.Printf("Error deleting announcement with UUID %s: %v", uuid, err) - return err - } - return nil + return s.q.DeleteAnnouncement(ctx, uuid) } From c46fb6cf0e1e9e62ff78f86cef712be20d515c1b Mon Sep 17 00:00:00 2001 From: Tomas Oh Date: Sun, 12 Apr 2026 19:39:50 -0500 Subject: [PATCH 3/3] remove print statement in handler --- internal/api/handlers/event.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/api/handlers/event.go b/internal/api/handlers/event.go index e3ad3792..3f989226 100644 --- a/internal/api/handlers/event.go +++ b/internal/api/handlers/event.go @@ -148,7 +148,6 @@ func (h *EventsHandler) UpdateEvent(c *gin.Context) { var params dbmodels.UpdateEventParams id := c.Param("id") - fmt.Println("UPDATE:", id) if err := c.ShouldBindJSON(¶ms); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid request body. " + err.Error(),