From 5c6f5fc697d08ec98a1dbf4b2c8eee5ce08c2e50 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Thu, 29 Oct 2020 18:17:04 -0700 Subject: [PATCH] Add logging admin APIs for dashboard #114 --- controllers/admin/logs.go | 50 ++++++++++++++++++++++++ logging/logging.go | 80 +++++++++++++++++++++++++++++++++++++++ main.go | 2 + router/middleware/auth.go | 2 - router/router.go | 6 +++ 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 controllers/admin/logs.go create mode 100644 logging/logging.go diff --git a/controllers/admin/logs.go b/controllers/admin/logs.go new file mode 100644 index 000000000..b6e9689ec --- /dev/null +++ b/controllers/admin/logs.go @@ -0,0 +1,50 @@ +package admin + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/owncast/owncast/logging" + "github.com/sirupsen/logrus" +) + +// GetLogs will return all logs +func GetLogs(w http.ResponseWriter, r *http.Request) { + logs := logging.Logger.AllEntries() + response := make([]logsResponse, 0) + + for i := 0; i < len(logs); i++ { + response = append(response, fromEntry(logs[i])) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// GetWarnings will return only warning and error logs +func GetWarnings(w http.ResponseWriter, r *http.Request) { + logs := logging.Logger.WarningEntries() + response := make([]logsResponse, 0) + + for i := 0; i < len(logs); i++ { + response = append(response, fromEntry(logs[i])) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +type logsResponse struct { + Message string `json:"message"` + Level string `json:"level"` + Time time.Time `json:"time"` +} + +func fromEntry(e *logrus.Entry) logsResponse { + return logsResponse{ + Message: e.Message, + Level: e.Level.String(), + Time: e.Time, + } +} diff --git a/logging/logging.go b/logging/logging.go new file mode 100644 index 000000000..639870ec3 --- /dev/null +++ b/logging/logging.go @@ -0,0 +1,80 @@ +package logging + +// Custom logging hooks for powering our logs API. +// Modeled after https://github.com/sirupsen/logrus/blob/master/hooks/test/test.go + +import ( + "os" + "sync" + + "github.com/sirupsen/logrus" + logger "github.com/sirupsen/logrus" +) + +const maxLogEntries = 500 + +type OCLogger struct { + Entries []logrus.Entry + mu sync.RWMutex +} + +var Logger *OCLogger + +// Setup configures our custom logging destinations +func Setup() { + logger.SetOutput(os.Stdout) // Send all logs to console + + _logger := new(OCLogger) + logger.AddHook(_logger) + + Logger = _logger +} + +// Fire runs for every logging request +func (l *OCLogger) Fire(e *logger.Entry) error { + // Store all log messages to return back in the logging API + l.mu.Lock() + defer l.mu.Unlock() + + if len(l.Entries) > maxLogEntries { + l.Entries = l.Entries[1:] + } + l.Entries = append(l.Entries, *e) + + return nil +} + +// Levels specifies what log levels we care about +func (l *OCLogger) Levels() []logrus.Level { + return logrus.AllLevels +} + +// AllEntries returns all entries that were logged. +func (l *OCLogger) AllEntries() []*logrus.Entry { + l.mu.RLock() + defer l.mu.RUnlock() + // Make a copy so the returned value won't race with future log requests + entries := make([]*logrus.Entry, len(l.Entries)) + for i := 0; i < len(l.Entries); i++ { + // Make a copy, for safety + entries[i] = &l.Entries[i] + } + + return entries +} + +// WarningEntries returns all warning or greater that were logged. +func (l *OCLogger) WarningEntries() []*logrus.Entry { + l.mu.RLock() + defer l.mu.RUnlock() + // Make a copy so the returned value won't race with future log requests + entries := make([]*logrus.Entry, 0) + for i := 0; i < len(l.Entries); i++ { + if l.Entries[i].Level <= logrus.WarnLevel { + // Make a copy, for safety + entries = append(entries, &l.Entries[i]) + } + } + + return entries +} diff --git a/main.go b/main.go index a4e7bdfbc..4f166ffa4 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" + "github.com/owncast/owncast/logging" "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" @@ -80,6 +81,7 @@ func getVersion() string { } func configureLogging() { + logging.Setup() log.SetFormatter(&log.TextFormatter{ FullTimestamp: true, }) diff --git a/router/middleware/auth.go b/router/middleware/auth.go index f462db366..2620a0452 100644 --- a/router/middleware/auth.go +++ b/router/middleware/auth.go @@ -40,8 +40,6 @@ func RequireAdminAuth(handler http.HandlerFunc) http.HandlerFunc { return } - // Success - log.Traceln("Authenticated request OK for", r.URL.Path, "from", r.RemoteAddr, r.UserAgent()) handler(w, r) } } diff --git a/router/router.go b/router/router.go index 6da168066..2423a467a 100644 --- a/router/router.go +++ b/router/router.go @@ -71,6 +71,12 @@ func Start() error { // Get a a detailed list of currently connected clients http.HandleFunc("/api/admin/clients", middleware.RequireAdminAuth(controllers.GetConnectedClients)) + // Get all logs + http.HandleFunc("/api/admin/logs", middleware.RequireAdminAuth(admin.GetLogs)) + + // Get warning/error logs + http.HandleFunc("/api/admin/logs/warnings", middleware.RequireAdminAuth(admin.GetWarnings)) + port := config.Config.GetPublicWebServerPort() log.Infof("Web server running on port: %d", port)