From 64820f0af5a03a65713d89d57339cdd75a4dcf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ma=C5=A1ek?= Date: Sat, 15 Feb 2025 23:42:15 +0100 Subject: [PATCH] replace log.Print* with Zap logger --- cmd/debug.go | 5 ++++- conf/config.go | 8 +++++--- handlers/html.go | 6 ++++-- handlers/mail.go | 12 +++++++----- handlers/report.go | 10 ++++++---- handlers/server.go | 6 +++--- handlers/server_gui.go | 21 ++++++++++++--------- handlers/status.go | 11 ++++++----- monitor/heartbeat_listener.go | 8 +++++--- monitor/web_pinger.go | 18 ++++++++++-------- scheduler/scheduler.go | 21 +++++++++++++-------- storage/storage.go | 5 +++-- 12 files changed, 78 insertions(+), 53 deletions(-) diff --git a/cmd/debug.go b/cmd/debug.go index e224494..9504701 100644 --- a/cmd/debug.go +++ b/cmd/debug.go @@ -1,8 +1,11 @@ package cmd import ( + "fmt" + "github.com/davidmasek/beacon/logging" "github.com/spf13/cobra" + "go.uber.org/zap" ) var debugCmd = &cobra.Command{ @@ -14,7 +17,7 @@ var debugCmd = &cobra.Command{ logger.Debugw("Debug message", "foo", 42) logger.Infow("Info message", "foo", 42) logger.Warnw("Warn message", "foo", 42) - logger.Errorw("Error message", "foo", 42) + logger.Errorw("Error message", zap.Error(fmt.Errorf("big bad"))) cmd.Println("Done") return nil }, diff --git a/conf/config.go b/conf/config.go index a32c02a..88f5fbe 100644 --- a/conf/config.go +++ b/conf/config.go @@ -5,13 +5,13 @@ import ( "errors" "fmt" "io/fs" - "log" "os" "path/filepath" "strings" "time" "github.com/caarlos0/env/v11" + "github.com/davidmasek/beacon/logging" "gopkg.in/yaml.v3" ) @@ -220,6 +220,7 @@ func (tz *TzLocation) UnmarshalYAML(value *yaml.Node) error { // Parse config from YAML and override using ENV variables func ConfigFromBytes(data []byte) (*Config, error) { + logger := logging.Get() config := NewConfig() err := yaml.Unmarshal(data, config) if err != nil { @@ -231,12 +232,13 @@ func ConfigFromBytes(data []byte) (*Config, error) { if err != nil { return nil, err } - log.Println(">>>>", config, "<<<<") + logger.Infow("loaded config", "config", config) return config, err } func configFromFile(configFile string) (*Config, error) { - log.Printf("reading config from %q\n", configFile) + logger := logging.Get() + logger.Infow("reading config from file", "path", configFile) data, err := os.ReadFile(configFile) if err != nil { return nil, err diff --git a/handlers/html.go b/handlers/html.go index 7c4bdc1..58f18b5 100644 --- a/handlers/html.go +++ b/handlers/html.go @@ -4,8 +4,9 @@ import ( "embed" "html/template" "io" - "log" "os" + + "github.com/davidmasek/beacon/logging" ) //go:embed report.template.html @@ -27,7 +28,8 @@ func WriteReport(reports []ServiceReport, wr io.Writer) error { } func WriteReportToFile(reports []ServiceReport, filename string) error { - log.Printf("Writing report to %s", filename) + logger := logging.Get() + logger.Infow("Writing report to file", "path", filename) // Create or truncate the output file file, err := os.Create(filename) if err != nil { diff --git a/handlers/mail.go b/handlers/mail.go index 35ba921..9a63a96 100644 --- a/handlers/mail.go +++ b/handlers/mail.go @@ -4,18 +4,19 @@ import ( "bytes" "crypto/tls" "fmt" - "log" "strings" "github.com/davidmasek/beacon/conf" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/monitor" "github.com/wneessen/go-mail" + "go.uber.org/zap" ) func SendReport(reports []ServiceReport, emailConfig *conf.EmailConfig) error { var buffer bytes.Buffer - - log.Printf("[SMTPMailer] Generating report") + logger := logging.Get() + logger.Info("Generating report") err := WriteReport(reports, &buffer) if err != nil { return err @@ -52,7 +53,8 @@ func SendReport(reports []ServiceReport, emailConfig *conf.EmailConfig) error { } func SendMail(emailConfig *conf.EmailConfig, subject string, body string) error { - log.Printf("Sending email with subject %q to %q", subject, emailConfig.SendTo) + logger := logging.Get() + logger.Infow("Sending email", "subject", subject, "to", emailConfig.SendTo) message := mail.NewMsg() if err := message.From(emailConfig.Sender); err != nil { @@ -87,7 +89,7 @@ func SendMail(emailConfig *conf.EmailConfig, subject string, body string) error err = client.DialAndSend(message) if err != nil { - log.Printf("Failed to send email: %v", err) + logger.Errorw("Failed to send email", "subject", subject, "to", emailConfig.SendTo, zap.Error(err)) } return err } diff --git a/handlers/report.go b/handlers/report.go index 99374ec..a349799 100644 --- a/handlers/report.go +++ b/handlers/report.go @@ -3,13 +3,14 @@ package handlers import ( "errors" "fmt" - "log" "strings" "time" "github.com/davidmasek/beacon/conf" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/monitor" "github.com/davidmasek/beacon/storage" + "go.uber.org/zap" ) // Calculate when the next report should happen based on last report time. @@ -43,6 +44,7 @@ func NextReportTime(config *conf.Config, lastReportTime time.Time) time.Time { } func GenerateReport(db storage.Storage, config *conf.Config) ([]ServiceReport, error) { + logger := logging.Get() reports := make([]ServiceReport, 0) services := config.AllServices() @@ -52,16 +54,16 @@ func GenerateReport(db storage.Storage, config *conf.Config) ([]ServiceReport, e } for _, service := range services { - log.Println("Checking service", service) + healthCheck, err := db.LatestHealthCheck(service.Id) var serviceStatus monitor.ServiceStatus if err == nil { serviceStatus = checkConfig.GetServiceStatus(healthCheck) } else { - log.Println("[ERROR]", err) + logger.Errorw("error checking service status", "service", service, zap.Error(err)) serviceStatus = monitor.STATUS_OTHER } - log.Println(" - Service status:", serviceStatus) + logger.Debug("Checked service", "service", service, "status", serviceStatus) reports = append(reports, ServiceReport{ ServiceId: service.Id, ServiceStatus: serviceStatus, LatestHealthCheck: healthCheck, diff --git a/handlers/server.go b/handlers/server.go index a9e8cd8..af23e5b 100644 --- a/handlers/server.go +++ b/handlers/server.go @@ -2,15 +2,16 @@ package handlers import ( "fmt" - "log" "net/http" "github.com/davidmasek/beacon/conf" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/monitor" "github.com/davidmasek/beacon/storage" ) func StartServer(db storage.Storage, config *conf.Config) (*http.Server, error) { + logger := logging.Get() mux := http.NewServeMux() mux.HandleFunc("/{$}", handleIndex(db, config)) @@ -27,8 +28,7 @@ func StartServer(db storage.Storage, config *conf.Config) (*http.Server, error) go func() { fmt.Printf("Starting UI server on http://localhost:%d\n", port) if err := server.ListenAndServe(); err != http.ErrServerClosed { - log.Print(err) - panic(err) + logger.Panic(err) } }() return server, nil diff --git a/handlers/server_gui.go b/handlers/server_gui.go index fefd070..7f8b254 100644 --- a/handlers/server_gui.go +++ b/handlers/server_gui.go @@ -4,15 +4,16 @@ import ( "embed" "fmt" "html/template" - "log" "net/http" "path/filepath" "runtime/debug" "time" "github.com/davidmasek/beacon/conf" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/monitor" "github.com/davidmasek/beacon/storage" + "go.uber.org/zap" ) //go:embed templates/* @@ -21,6 +22,7 @@ var TEMPLATES embed.FS // Show services status func handleIndex(db storage.Storage, config *conf.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + logger := logging.Get() // Prepare a map to hold services and their heartbeats type ServiceStatus struct { // Needed as HealthCheck can be nil @@ -31,10 +33,10 @@ func handleIndex(db storage.Storage, config *conf.Config) http.HandlerFunc { var services []ServiceStatus serviceChecker := DefaultServiceChecker() for _, serviceCfg := range config.AllServices() { - log.Println("Querying", serviceCfg.Id) + logger.Debugw("Querying", "service", serviceCfg.Id) healthCheck, err := db.LatestHealthCheck(serviceCfg.Id) if err != nil { - log.Printf("Failed to load health check: %s", err) + logger.Errorw("Failed to load health check", "service", serviceCfg.Id, zap.Error(err)) http.Error(w, "Failed to load health check", http.StatusInternalServerError) return } @@ -73,7 +75,7 @@ func handleIndex(db storage.Storage, config *conf.Config) http.HandlerFunc { filepath.Join("templates", "common.css"), ) if err != nil { - log.Printf("Error parsing template: %v", err) + logger.Errorw("Error parsing template", zap.Error(err)) http.Error(w, "Failed to render page", http.StatusInternalServerError) return } @@ -83,7 +85,7 @@ func handleIndex(db storage.Storage, config *conf.Config) http.HandlerFunc { "CurrentPage": "home", }) if err != nil { - log.Println("Failed to render", err) + logger.Errorw("Error rendering template", zap.Error(err)) http.Error(w, "Failed to render page", http.StatusInternalServerError) } } @@ -92,6 +94,7 @@ func handleIndex(db storage.Storage, config *conf.Config) http.HandlerFunc { // Show services status func handleAbout(db storage.Storage, config *conf.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + logger := logging.Get() tmpl := template.New("about.html") tmpl, err := tmpl.ParseFS(TEMPLATES, filepath.Join("templates", "about.html"), @@ -99,7 +102,7 @@ func handleAbout(db storage.Storage, config *conf.Config) http.HandlerFunc { filepath.Join("templates", "common.css"), ) if err != nil { - log.Printf("Error parsing template: %v", err) + logger.Errorw("Error parsing template", zap.Error(err)) http.Error(w, "Failed to render page", http.StatusInternalServerError) return } @@ -107,7 +110,7 @@ func handleAbout(db storage.Storage, config *conf.Config) http.HandlerFunc { timeFormat := "15:04 Monday 02 January" lastReport, err := db.LatestTaskLog("report") if err != nil { - log.Println("DB error", err) + logger.Errorw("DB error", zap.Error(err)) http.Error(w, "Server error, please try again later", http.StatusInternalServerError) return } @@ -128,7 +131,7 @@ func handleAbout(db storage.Storage, config *conf.Config) http.HandlerFunc { if lastReport == nil { lastReportTime = "never" nextReportAfter = "error" - log.Println("DB not properly initialized! No report task found") + logger.Error("DB not properly initialized! No report task found") } else if lastReport.Status == string(TASK_SENTINEL) { lastReportTime = "never" nextReportAfter = NextReportTime(config, lastReport.Timestamp). @@ -160,7 +163,7 @@ func handleAbout(db storage.Storage, config *conf.Config) http.HandlerFunc { "BeaconVersion": beaconVersion, }) if err != nil { - log.Println("Failed to render", err) + logger.Errorw("Failed to render", zap.Error(err)) http.Error(w, "Failed to render page", http.StatusInternalServerError) } } diff --git a/handlers/status.go b/handlers/status.go index b90c7c6..07f1c9a 100644 --- a/handlers/status.go +++ b/handlers/status.go @@ -1,9 +1,9 @@ package handlers import ( - "log" "time" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/monitor" "github.com/davidmasek/beacon/storage" ) @@ -22,25 +22,26 @@ func DefaultServiceChecker() ServiceChecker { // // TODO: maybe should be used like HealthCheck.GetStatus(config) or smth func (config *ServiceChecker) GetServiceStatus(latestHealthCheck *storage.HealthCheck) monitor.ServiceStatus { + logger := logging.Get() if latestHealthCheck == nil { - log.Println("[GetServiceStatus] no health check found") + logger.Debug("[GetServiceStatus] no health check found") return monitor.STATUS_FAIL } timeAgo := time.Since(latestHealthCheck.Timestamp) - log.Printf("timeout: %s, timeAgo: %s", config.Timeout.String(), timeAgo.String()) + logger.Debug("timeout: %s, timeAgo: %s", config.Timeout.String(), timeAgo.String()) if timeAgo > config.Timeout { return monitor.STATUS_FAIL } if errorMeta, exists := latestHealthCheck.Metadata["error"]; exists { if errorMeta != "" { - log.Printf("[GetServiceStatus] error found: %q", errorMeta) + logger.Debug("[GetServiceStatus] error found: %q", errorMeta) return monitor.STATUS_FAIL } } if statusMeta, exists := latestHealthCheck.Metadata["status"]; exists { if statusMeta != string(monitor.STATUS_OK) { - log.Printf("[GetServiceStatus] status not OK: %q != %q", statusMeta, string(monitor.STATUS_OK)) + logger.Debug("[GetServiceStatus] status not OK: %q != %q", statusMeta, string(monitor.STATUS_OK)) return monitor.STATUS_FAIL } } diff --git a/monitor/heartbeat_listener.go b/monitor/heartbeat_listener.go index a2a05de..6e692fb 100644 --- a/monitor/heartbeat_listener.go +++ b/monitor/heartbeat_listener.go @@ -2,12 +2,12 @@ package monitor import ( "encoding/json" - "log" "net/http" "time" _ "github.com/mattn/go-sqlite3" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/storage" ) @@ -29,6 +29,7 @@ func RegisterHeartbeatHandlers(db storage.Storage, mux *http.ServeMux) { func handleBeat(db storage.Storage) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + logger := logging.Get() serviceId := r.PathValue("service_id") if serviceId == "" { http.Error(w, "Missing service_id", http.StatusBadRequest) @@ -38,7 +39,7 @@ func handleBeat(db storage.Storage) http.HandlerFunc { // Log the heartbeat to the database nowStr, err := db.RecordHeartbeat(serviceId, now) if err != nil { - log.Println("[ERROR]", err) + logger.Error(err) http.Error(w, "Failed to log heartbeat", http.StatusInternalServerError) return } @@ -57,6 +58,7 @@ func handleBeat(db storage.Storage) http.HandlerFunc { func handleStatus(db storage.Storage) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + logger := logging.Get() serviceId := r.PathValue("service_id") if serviceId == "" { http.Error(w, "Missing service_id", http.StatusBadRequest) @@ -66,7 +68,7 @@ func handleStatus(db storage.Storage) http.HandlerFunc { // Query the database for the latest heartbeat timestamps, err := db.GetLatestHeartbeats(serviceId, 1) if err != nil { - log.Println("[ERROR]", err) + logger.Error(err) http.Error(w, "Failed to query database", http.StatusInternalServerError) return } diff --git a/monitor/web_pinger.go b/monitor/web_pinger.go index 742645c..e349d1f 100644 --- a/monitor/web_pinger.go +++ b/monitor/web_pinger.go @@ -1,7 +1,6 @@ package monitor import ( - "log" "time" "io" @@ -9,7 +8,9 @@ import ( "slices" "strings" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/storage" + "go.uber.org/zap" ) type WebConfig struct { @@ -19,15 +20,15 @@ type WebConfig struct { } func CheckWebsites(db storage.Storage, websites map[string]WebConfig) error { + logger := logging.Get() for service, config := range websites { - log.Println("Checking website", service) - log.Printf("Config: %+v\n", config) + logger.Debugw("Checking website", "service", service, "check_config", config) timtestamp := time.Now() serviceStatus, err := config.GetServiceStatus() metadata := make(map[string]string) metadata["status"] = string(serviceStatus) if err != nil { - log.Println("[ERROR]", err) + logger.Error(err) metadata["error"] = err.Error() } healthCheck := &storage.HealthCheckInput{ @@ -35,12 +36,12 @@ func CheckWebsites(db storage.Storage, websites map[string]WebConfig) error { Timestamp: timtestamp, Metadata: metadata, } - log.Printf("Saving %+v\n", healthCheck) + logger.Debugw("Saving", "healthCheck", healthCheck) err = db.AddHealthCheck( healthCheck, ) if err != nil { - log.Println("[ERROR] Unable to save HealthCheck", err) + logger.Errorw("Unable to save HealthCheck", zap.Error(err), "healthCheck", healthCheck, "service", service) return err } } @@ -49,6 +50,7 @@ func CheckWebsites(db storage.Storage, websites map[string]WebConfig) error { } func (config *WebConfig) GetServiceStatus() (ServiceStatus, error) { + logger := logging.Get() // TODO: we need to split this into two functions // - "get status from website to DB" // - "get ServiceStatus based on info from DB" @@ -60,7 +62,7 @@ func (config *WebConfig) GetServiceStatus() (ServiceStatus, error) { defer resp.Body.Close() codeOk := slices.Contains(config.HttpStatus, resp.StatusCode) if !codeOk { - log.Printf("Expected status code %v, got %v", config.HttpStatus, resp.StatusCode) + logger.Debugw("Web check - Unexpected status code", "expected", config.HttpStatus, "got", resp.StatusCode) return STATUS_FAIL, err } body, err := io.ReadAll(resp.Body) @@ -72,7 +74,7 @@ func (config *WebConfig) GetServiceStatus() (ServiceStatus, error) { contained := strings.Contains(string(body), content) if !contained { fail = true - log.Printf("Expected body to contain %q, but it didn't", content) + logger.Debugw("Web check - Unexpected body", "expected", contained) break } } diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index b98f288..23922b1 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -2,13 +2,14 @@ package scheduler import ( "context" - "log" "time" "github.com/davidmasek/beacon/conf" "github.com/davidmasek/beacon/handlers" + "github.com/davidmasek/beacon/logging" "github.com/davidmasek/beacon/monitor" "github.com/davidmasek/beacon/storage" + "go.uber.org/zap" ) func ShouldCheckWebServices(db storage.Storage, config *conf.Config, now time.Time) bool { @@ -42,6 +43,7 @@ func CheckWebServices(db storage.Storage, services []conf.ServiceConfig) error { // Add placeholder (sentinel) "report" task to bootstrap calculation of next report time. func InitializeSentinel(db storage.Storage, now time.Time) error { + logger := logging.Get() task, err := db.LatestTaskLog("report") if err != nil { return err @@ -50,7 +52,7 @@ func InitializeSentinel(db storage.Storage, now time.Time) error { if task != nil { return nil } - log.Printf("Creating sentinel report task with time %s\n", now) + logger.Infow("Creating sentinel report task", "time", now) err = db.CreateTaskLog(storage.TaskInput{ TaskName: "report", Status: string(handlers.TASK_SENTINEL), Timestamp: now, Details: ""}) return err @@ -80,11 +82,12 @@ func ShouldReport(db storage.Storage, config *conf.Config, query time.Time) (boo } func RunSingle(db storage.Storage, config *conf.Config, now time.Time) error { - log.Println("Do scheduling work") + logger := logging.Get() + logger.Info("Do scheduling work") var err error = nil if ShouldCheckWebServices(db, config, now) { - log.Println("Checking web services...") + logger.Info("Checking web services...") err = CheckWebServices(db, config.AllServices()) // TODO: might want to continue on error here if err != nil { @@ -96,7 +99,7 @@ func RunSingle(db storage.Storage, config *conf.Config, now time.Time) error { return err } if doReport { - log.Println("Reporting...") + logger.Info("Reporting...") err = handlers.DoReportTask(db, config, now) } return err @@ -108,21 +111,23 @@ func RunSingle(db storage.Storage, config *conf.Config, now time.Time) error { // Will not call run next job again until previous one returns, even // if specified interval passes. func Start(ctx context.Context, db storage.Storage, config *conf.Config) { + logger := logging.Get() checkInterval := config.SchedulerPeriod InitializeSentinel(db, time.Now()) - log.Printf("Starting scheduler: run each %s\n", checkInterval) + logger.Info("Starting scheduler: run each %s\n", checkInterval) startFunction(ctx, checkInterval, func(now time.Time) error { return RunSingle(db, config, now) }) } func startFunction(ctx context.Context, interval time.Duration, job func(time.Time) error) { + logger := logging.Get() ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): - log.Println("Scheduler stopped.") + logger.Info("Scheduler stopped.") return case t := <-ticker.C: // If both ctx.Done() and ticker.C are ready @@ -133,7 +138,7 @@ func startFunction(ctx context.Context, interval time.Duration, job func(time.Ti } err := job(t) if err != nil { - log.Println("[scheduler:error]", err) + logger.Errorw("scheduled job failed", zap.Error(err)) } } } diff --git a/storage/storage.go b/storage/storage.go index 2c40870..f5bae74 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -6,12 +6,12 @@ import ( "encoding/json" "errors" "fmt" - "log" "os" "path/filepath" "testing" "time" + "github.com/davidmasek/beacon/logging" _ "github.com/mattn/go-sqlite3" ) @@ -368,6 +368,7 @@ func NewSQLStorage(path string) (*SQLStorage, error) { // If BEACON_DB is set that will be used. // Otherwise homedir/beacon.db is used. func InitDB(dbPath string) (Storage, error) { + logger := logging.Get() // If not specified try loading from env variable. // Always falling back to env var makes the path // rewritable independent on how config is loaded. @@ -389,7 +390,7 @@ func InitDB(dbPath string) (Storage, error) { } dbPath = filepath.Join(homedir, "beacon.db") } - log.Println("DB path:", dbPath) + logger.Infow("Initializing DB", "path", dbPath) db, err := NewSQLStorage(dbPath) if err != nil { return nil, err