diff --git a/cmd/server/main.go b/cmd/server/main.go index f9d1c2b..1b14e3d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -125,8 +125,8 @@ type realtimeInfra struct { presence *websocket.PresenceChecker } -func setupWebSocket(streamerRepo domain.StreamerRepository, redisAddr string, configCache domain.ConfigSource, wsMetrics *metrics.WebSocketMetrics) (realtimeInfra, error) { - node, err := websocket.NewNode(streamerRepo, wsMetrics) +func setupWebSocket(streamerRepo domain.StreamerRepository, redisAddr string, configCache domain.ConfigSource, wsMetrics *metrics.WebSocketMetrics, logLevel string) (realtimeInfra, error) { + node, err := websocket.NewNode(streamerRepo, wsMetrics, logLevel) if err != nil { return realtimeInfra{}, fmt.Errorf("create Centrifuge node: %w", err) } @@ -236,7 +236,7 @@ func main() { ovl := app.NewOverlay(rc.configCache, rc.sentiment, rc.debouncer) - ws, err := setupWebSocket(db.streamerRepo, rc.client.Options().Addr, rc.configCache, wsMetrics) + ws, err := setupWebSocket(db.streamerRepo, rc.client.Options().Addr, rc.configCache, wsMetrics, cfg.LogLevel) if err != nil { slog.Error("WebSocket setup failed", "error", err) os.Exit(1) diff --git a/internal/adapter/httpserver/handlers_api.go b/internal/adapter/httpserver/handlers_api.go index 53ceef0..8440485 100644 --- a/internal/adapter/httpserver/handlers_api.go +++ b/internal/adapter/httpserver/handlers_api.go @@ -52,6 +52,8 @@ func (s *Server) handleResetSentiment(c echo.Context) error { return apperrors.InternalError("failed to reset sentiment", err).WithField("overlay_uuid", overlayUUID.String()) } + slog.InfoContext(ctx, "Sentiment reset", "streamer_id", streamerID, "overlay_uuid", overlayUUID) + if err := c.JSON(http.StatusOK, map[string]string{"status": "ok"}); err != nil { return fmt.Errorf("failed to send JSON response: %w", err) } @@ -97,6 +99,8 @@ func (s *Server) handleRotateOverlayUUID(c echo.Context) error { return apperrors.InternalError("failed to rotate overlay UUID", err).WithField("streamer_id", streamerID.String()) } + slog.InfoContext(ctx, "Overlay UUID rotated", "streamer_id", streamerID, "new_overlay_uuid", newUUID) + newURL := fmt.Sprintf("%s/overlay/%s", s.getBaseURL(c), newUUID) response := map[string]string{ "status": "ok", diff --git a/internal/adapter/httpserver/handlers_auth.go b/internal/adapter/httpserver/handlers_auth.go index de3676e..bf94eb3 100644 --- a/internal/adapter/httpserver/handlers_auth.go +++ b/internal/adapter/httpserver/handlers_auth.go @@ -173,6 +173,8 @@ func (s *Server) handleOAuthCallback(c echo.Context) error { return apperrors.InternalError("failed to save session", err) } + slog.InfoContext(ctx, "Streamer logged in", "streamer_id", streamer.ID, "broadcaster_id", result.UserID, "twitch_username", result.Username) + if err := c.Redirect(http.StatusFound, "/dashboard"); err != nil { return fmt.Errorf("failed to redirect: %w", err) } @@ -180,6 +182,9 @@ func (s *Server) handleOAuthCallback(c echo.Context) error { } func (s *Server) handleLogout(c echo.Context) error { + ctx := c.Request().Context() + streamerID, _ := c.Get("userID").(uuid.UUID) + session, err := s.sessionStore.Get(c.Request(), sessionName) if err != nil { slog.Error("Failed to get session during logout", "error", err) @@ -194,6 +199,8 @@ func (s *Server) handleLogout(c echo.Context) error { return apperrors.InternalError("failed to save logout session", err) } + slog.InfoContext(ctx, "Streamer logged out", "streamer_id", streamerID) + if err := c.Redirect(http.StatusFound, "/auth/login"); err != nil { return fmt.Errorf("failed to redirect: %w", err) } diff --git a/internal/adapter/httpserver/handlers_dashboard.go b/internal/adapter/httpserver/handlers_dashboard.go index f82931d..7bb78b2 100644 --- a/internal/adapter/httpserver/handlers_dashboard.go +++ b/internal/adapter/httpserver/handlers_dashboard.go @@ -3,6 +3,7 @@ package httpserver import ( "errors" "fmt" + "log/slog" "net/http" "strconv" "strings" @@ -170,6 +171,8 @@ func (s *Server) handleSaveConfig(c echo.Context) error { WithField("broadcaster_id", streamer.TwitchUserID) } + slog.InfoContext(ctx, "Config updated", "streamer_id", streamerID, "broadcaster_id", streamer.TwitchUserID) + // AJAX requests get a 204; browser form submissions get a redirect if c.Request().Header.Get("X-Requested-With") == "XMLHttpRequest" { if err := c.NoContent(http.StatusNoContent); err != nil { diff --git a/internal/adapter/httpserver/middleware.go b/internal/adapter/httpserver/middleware.go index 29c960f..b31956f 100644 --- a/internal/adapter/httpserver/middleware.go +++ b/internal/adapter/httpserver/middleware.go @@ -7,9 +7,18 @@ import ( "net/http" "github.com/labstack/echo/v4" + "github.com/pscheid92/chatpulse/internal/platform/correlation" apperrors "github.com/pscheid92/chatpulse/internal/platform/errors" ) +func correlationMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + ctx := correlation.WithID(c.Request().Context(), correlation.NewID()) + c.SetRequest(c.Request().WithContext(ctx)) + return next(c) + } +} + func ErrorHandlingMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { diff --git a/internal/adapter/httpserver/routes.go b/internal/adapter/httpserver/routes.go index eb26df1..1c2f8fd 100644 --- a/internal/adapter/httpserver/routes.go +++ b/internal/adapter/httpserver/routes.go @@ -9,6 +9,7 @@ import ( ) func (s *Server) registerRoutes() { + s.echo.Use(correlationMiddleware) if s.httpMetrics != nil { s.echo.Use(s.httpMetrics.Middleware()) } diff --git a/internal/adapter/websocket/node.go b/internal/adapter/websocket/node.go index c75531c..85d5d39 100644 --- a/internal/adapter/websocket/node.go +++ b/internal/adapter/websocket/node.go @@ -11,8 +11,8 @@ import ( "github.com/pscheid92/uuid" ) -func NewNode(userRepo domain.StreamerRepository, wsMetrics *metrics.WebSocketMetrics) (*centrifuge.Node, error) { - conf := centrifuge.Config{LogLevel: centrifuge.LogLevelInfo, LogHandler: slogHandler} +func NewNode(userRepo domain.StreamerRepository, wsMetrics *metrics.WebSocketMetrics, logLevel string) (*centrifuge.Node, error) { + conf := centrifuge.Config{LogLevel: parseCentrifugeLogLevel(logLevel), LogHandler: slogHandler} node, err := centrifuge.New(conf) if err != nil { return nil, fmt.Errorf("create centrifuge node: %w", err) @@ -123,6 +123,19 @@ func slogHandler(entry centrifuge.LogEntry) { } } +func parseCentrifugeLogLevel(level string) centrifuge.LogLevel { + switch level { + case "debug": + return centrifuge.LogLevelDebug + case "warn": + return centrifuge.LogLevelWarn + case "error": + return centrifuge.LogLevelError + default: + return centrifuge.LogLevelInfo + } +} + type PresenceChecker struct { node *centrifuge.Node }