From 5a904772a866447c614fd6b94768c99ff69daf72 Mon Sep 17 00:00:00 2001 From: Kohki Makimoto Date: Tue, 18 Nov 2025 13:34:42 +0900 Subject: [PATCH] support polling option --- README.md | 4 +- demo/main.go | 16 ++++-- ssehelpers.go => helpers.go | 26 +++++++++ monitors/errors.go | 29 +++++++--- monitors/errors.html | 105 ++++++++++++++++++++++++++++++++--- monitors/logs.go | 23 +++++++- monitors/logs.html | 105 ++++++++++++++++++++++++++++++++--- monitors/queries.go | 27 +++++++-- monitors/queries.html | 105 ++++++++++++++++++++++++++++++++--- monitors/requests.go | 13 ++++- monitors/requests.html | 107 +++++++++++++++++++++++++++++++++--- monitors/writer.go | 47 +++++++++++++--- monitors/writer.html | 105 ++++++++++++++++++++++++++++++++--- store_test.go | 54 ------------------ 14 files changed, 649 insertions(+), 117 deletions(-) rename ssehelpers.go => helpers.go (68%) diff --git a/README.md b/README.md index bb07331..28aa86f 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ func main() { m.AddMonitor(requestsMonitor) // Add logs monitor - logsMonitor, wrappedLogger := monitors.NewLogsMonitor(e.Logger) + logsMonitor, wrappedLogger := monitors.NewLogsMonitor(monitors.LogsMonitorConfig{ + Logger: e.Logger, + }) e.Logger = wrappedLogger m.AddMonitor(logsMonitor) diff --git a/demo/main.go b/demo/main.go index b14cc59..19f0bb6 100644 --- a/demo/main.go +++ b/demo/main.go @@ -34,14 +34,19 @@ func main() { // ---------------------------------------------- // logs monitor // ---------------------------------------------- - logsMonitor, wrappedLogger := monitors.NewLogsMonitor(e.Logger) + logsMonitor, wrappedLogger := monitors.NewLogsMonitor(monitors.LogsMonitorConfig{ + Logger: e.Logger, + }) + // Replace the Echo logger with the wrapped logger e.Logger = wrappedLogger m.AddMonitor(logsMonitor) // ---------------------------------------------- // writer monitor // ---------------------------------------------- - m.AddMonitor(monitors.NewLoggerWriterMonitor(e.Logger)) + m.AddMonitor(monitors.NewLoggerWriterMonitor(monitors.LoggerWriterMonitorConfig{ + Logger: e.Logger, + })) // ---------------------------------------------- // queries monitor @@ -56,7 +61,10 @@ func main() { // Wrap the database driver with query monitoring var queriesMonitor *debugmonitor.Monitor - queriesMonitor, db = monitors.NewQueriesMonitor(dsn, db.Driver()) + queriesMonitor, db = monitors.NewQueriesMonitor(monitors.QueriesMonitorConfig{ + DSN: dsn, + Driver: db.Driver(), + }) m.AddMonitor(queriesMonitor) // Initialize database schema @@ -65,7 +73,7 @@ func main() { // ---------------------------------------------- // errors monitor // ---------------------------------------------- - errorsMonitor, errorRecorder := monitors.NewErrorsMonitor() + errorsMonitor, errorRecorder := monitors.NewErrorsMonitor(monitors.ErrorsMonitorConfig{}) m.AddMonitor(errorsMonitor) // Wrap the default error handler to record errors diff --git a/ssehelpers.go b/helpers.go similarity index 68% rename from ssehelpers.go rename to helpers.go index b82cd53..2f2f46a 100644 --- a/ssehelpers.go +++ b/helpers.go @@ -1,8 +1,10 @@ package debugmonitor import ( + "bytes" "encoding/json" "fmt" + "html/template" "net/http" "strconv" "time" @@ -10,6 +12,15 @@ import ( "github.com/labstack/echo/v4" ) +// RenderTemplate executes a template with the given data and returns the result as HTML response. +func RenderTemplate(c echo.Context, tmpl *template.Template, data any) error { + var buf bytes.Buffer + if err := tmpl.Execute(&buf, data); err != nil { + return err + } + return c.HTML(http.StatusOK, buf.String()) +} + func HandleSSEStream(c echo.Context, store *Store) error { // Parse the sinceID parameter sinceID := int64(0) @@ -82,3 +93,18 @@ func sendSSEEvent(c echo.Context, entry *DataEntry) error { _, err = fmt.Fprintf(c.Response().Writer, "data: %s\n\n", data) return err } + +// HandleDataJSON returns store entries as JSON for polling mode. +// It accepts a "since" query parameter to return only entries with ID greater than the specified value. +func HandleDataJSON(c echo.Context, store *Store) error { + // Parse the sinceID parameter + sinceID := int64(0) + if sinceIDStr := c.QueryParam("since"); sinceIDStr != "" { + if id, err := strconv.ParseInt(sinceIDStr, 10, 64); err == nil { + sinceID = id + } + } + + entries := store.GetSince(sinceID) + return c.JSON(http.StatusOK, entries) +} diff --git a/monitors/errors.go b/monitors/errors.go index 2c5a513..5725826 100644 --- a/monitors/errors.go +++ b/monitors/errors.go @@ -3,6 +3,7 @@ package monitors import ( _ "embed" "fmt" + "html/template" "net/http" "time" @@ -22,12 +23,21 @@ type ErrorPayload struct { //go:embed errors.html var errorsView string +// errorsViewTemplate is the parsed template for the errors view +var errorsViewTemplate = template.Must(template.New("errorsView").Parse(errorsView)) + // ErrorRecorder is a function type for recording errors type ErrorRecorder func(err error) +// ErrorsMonitorConfig defines the config for Errors monitor. +type ErrorsMonitorConfig struct { + // UsePolling enables polling mode instead of SSE for real-time updates. + UsePolling bool +} + // NewErrorsMonitor creates a new monitor for errors and returns // the monitor along with an error recording function -func NewErrorsMonitor() (*debugmonitor.Monitor, ErrorRecorder) { +func NewErrorsMonitor(config ErrorsMonitorConfig) (*debugmonitor.Monitor, ErrorRecorder) { m := &debugmonitor.Monitor{ Name: "errors", DisplayName: "Errors", @@ -36,10 +46,15 @@ func NewErrorsMonitor() (*debugmonitor.Monitor, ErrorRecorder) { ActionHandler: func(c echo.Context, store *debugmonitor.Store, action string) error { switch action { case "render": - return c.HTML(http.StatusOK, errorsView) + return debugmonitor.RenderTemplate(c, errorsViewTemplate, map[string]any{ + "UsePolling": config.UsePolling, + }) case "stream": // SSE endpoint for real-time updates return debugmonitor.HandleSSEStream(c, store) + case "data": + // JSON endpoint for polling mode + return debugmonitor.HandleDataJSON(c, store) default: return echo.NewHTTPError(http.StatusBadRequest) } @@ -123,11 +138,11 @@ func containsStackTrace(s string) bool { // Simple heuristic: stack traces usually contain file paths with line numbers // Look for patterns like ".go:" which are common in Go stack traces return len(s) > 100 && ( - // Common patterns in stack traces - containsPattern(s, ".go:") || - containsPattern(s, "goroutine ") || - containsPattern(s, "\tat ") || - containsPattern(s, "\n\t")) + // Common patterns in stack traces + containsPattern(s, ".go:") || + containsPattern(s, "goroutine ") || + containsPattern(s, "\tat ") || + containsPattern(s, "\n\t")) } // containsPattern checks if a string contains a specific pattern diff --git a/monitors/errors.html b/monitors/errors.html index d564623..b940f2c 100644 --- a/monitors/errors.html +++ b/monitors/errors.html @@ -1,4 +1,4 @@ -
+
@@ -105,18 +105,52 @@