From d47247bf5a49274abc332cd05c343cc64c39d110 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Thu, 6 Mar 2025 15:46:42 -0900 Subject: [PATCH] add better logging --- examples/main.go | 20 ++++++-- web/server.go | 80 ++++++++++++++++++++++---------- web/templates/index.templ | 88 +++++++++++++++++++++++------------- web/templates/index_templ.go | 6 +-- 4 files changed, 131 insertions(+), 63 deletions(-) diff --git a/examples/main.go b/examples/main.go index 5b23e2b..4bd9759 100644 --- a/examples/main.go +++ b/examples/main.go @@ -12,11 +12,20 @@ import ( "syscall" "time" + "log/slog" "track-gopher/derby" "track-gopher/web" ) func main() { + // Create logger + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) + + // Use the logger + logger.Info("Starting derby race application") + // Parse command line flags portName := flag.String("port", "/dev/ttyACM0", "Serial port for the derby clock") baudRate := flag.Int("baud", 19200, "Baud rate for the serial port") @@ -92,10 +101,14 @@ func main() { // startWebInterface initializes and runs the web interface func startWebInterface(clock *derby.DerbyClock, events <-chan derby.Event, webPort int, ctx context.Context) { + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) + // Create and start the web server server, err := web.NewServer(clock, events, webPort) if err != nil { - fmt.Printf("Error creating web server: %v\n", err) + logger.Error("Error creating web server", "error", err) return } @@ -103,7 +116,7 @@ func startWebInterface(clock *derby.DerbyClock, events <-chan derby.Event, webPo // Start the web server if err := server.Start(); err != nil { - fmt.Printf("Web server error: %v\n", err) + logger.Error("Web server error", "error", err) return } @@ -111,9 +124,8 @@ func startWebInterface(clock *derby.DerbyClock, events <-chan derby.Event, webPo <-ctx.Done() // Gracefully shut down the server - fmt.Println("Shutting down web server...") if err := server.Stop(); err != nil { - fmt.Printf("Error shutting down web server: %v\n", err) + logger.Error("Error shutting down web server", "error", err) } } diff --git a/web/server.go b/web/server.go index c228291..decd680 100644 --- a/web/server.go +++ b/web/server.go @@ -7,8 +7,10 @@ import ( "fmt" "io/fs" "log" + "log/slog" "net/http" "net/url" + "os" "strings" "sync" "time" @@ -33,10 +35,16 @@ type Server struct { port int server *http.Server shutdown chan struct{} + logger *slog.Logger } // NewServer creates a new web server func NewServer(clock *derby.DerbyClock, events <-chan derby.Event, port int) (*Server, error) { + // Create logger + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + })) + // Create server s := &Server{ router: chi.NewRouter(), @@ -46,6 +54,7 @@ func NewServer(clock *derby.DerbyClock, events <-chan derby.Event, port int) (*S clientsMux: sync.Mutex{}, port: port, shutdown: make(chan struct{}), + logger: logger, } // Set up routes @@ -115,8 +124,9 @@ func (s *Server) Start() error { // Start server in a goroutine go func() { + s.logger.Info("Web server starting", "port", s.port) if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - fmt.Printf("HTTP server error: %v\n", err) + s.logger.Error("HTTP server error", "error", err) } }() @@ -140,6 +150,8 @@ func (s *Server) Stop() error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + s.logger.Info("Shutting down web server") + // Shutdown the HTTP server if s.server != nil { return s.server.Shutdown(ctx) @@ -170,6 +182,7 @@ func (s *Server) broadcastEvent(event derby.Event) { switch event.Type { case derby.EventRaceStart: + s.logger.Info("Broadcasting race start event") statusMsg := struct { Status string `json:"status"` }{ @@ -179,6 +192,11 @@ func (s *Server) broadcastEvent(event derby.Event) { message = fmt.Sprintf("event: status\ndata: %s", statusJSON) case derby.EventLaneFinish: + s.logger.Info("Broadcasting lane finish event", + "lane", event.Result.Lane, + "time", event.Result.Time, + "place", event.Result.FinishPlace) + // Create a message for lane finish laneData := struct { Lane int `json:"lane"` @@ -193,6 +211,7 @@ func (s *Server) broadcastEvent(event derby.Event) { message = fmt.Sprintf("event: lane-finish\ndata: %s", laneJSON) case derby.EventRaceComplete: + s.logger.Info("Broadcasting race complete event") statusMsg := struct { Status string `json:"status"` }{ @@ -208,13 +227,20 @@ func (s *Server) broadcastEvent(event derby.Event) { // Send to all clients s.clientsMux.Lock() + clientCount := len(s.clients) + sentCount := 0 for clientChan := range s.clients { select { case clientChan <- message: + sentCount++ default: - // Client channel is full, could log this + s.logger.Warn("Client channel is full, event not sent") } } + s.logger.Info("Event broadcast complete", + "sentCount", sentCount, + "totalClients", clientCount, + "eventType", event.Type) s.clientsMux.Unlock() } @@ -280,51 +306,55 @@ func (s *Server) handleEvents() http.HandlerFunc { w.Header().Set("Connection", "keep-alive") w.Header().Set("Access-Control-Allow-Origin", "*") + // Debug message to confirm connection + fmt.Fprintf(w, "event: debug\ndata: {\"message\":\"SSE connection established\"}\n\n") + + // Flush headers to ensure they're sent to the client + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } else { + http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) + return + } + // Create a channel for this client clientChan := make(chan string, 10) // Add client to map with mutex protection s.clientsMux.Lock() s.clients[clientChan] = true + clientCount := len(s.clients) s.clientsMux.Unlock() + s.logger.Info("New client connected", + "clientIP", r.RemoteAddr, + "totalClients", clientCount) + // Remove client when connection is closed defer func() { s.clientsMux.Lock() delete(s.clients, clientChan) + remainingClients := len(s.clients) s.clientsMux.Unlock() close(clientChan) - }() - - // Send initial status - status := s.clock.Status() - var statusStr string - switch status { - case derby.StatusIdle: - statusStr = "idle" - case derby.StatusRunning: - statusStr = "running" - case derby.StatusFinished: - statusStr = "finished" - } - fmt.Fprintf(w, "event: status\ndata: {\"status\": \"%s\"}\n\n", statusStr) - w.(http.Flusher).Flush() + s.logger.Info("Client disconnected", + "clientIP", r.RemoteAddr, + "remainingClients", remainingClients) + }() - // Keep the connection open + // Keep connection open and send events as they arrive for { select { - case message, ok := <-clientChan: + case msg, ok := <-clientChan: if !ok { return } - - // Send the message to the client - fmt.Fprint(w, message) - w.(http.Flusher).Flush() - + fmt.Fprintf(w, "%s\n\n", msg) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } case <-r.Context().Done(): - // Client disconnected return } } diff --git a/web/templates/index.templ b/web/templates/index.templ index e9c2bfd..fbe4032 100644 --- a/web/templates/index.templ +++ b/web/templates/index.templ @@ -41,48 +41,74 @@ templ Index() { ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -104,7 +104,7 @@ func laneComponent(lane int) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("lane-" + string(rune('0'+lane))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 105, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 131, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -117,7 +117,7 @@ func laneComponent(lane int) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(string(rune('0' + lane))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 106, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 132, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil {