refactor: unify logging with log/slog
Replace three uncoordinated logging systems (log.Printf, fmt.Fprintf to stderr, fmt.Println warnings) with structured slog calls. 68 log calls migrated: - log.Printf → slog.Error/Warn/Info (45 calls across 5 files) - fmt.Fprintf(os.Stderr) → slog.Error (23 calls in main.go) Kept unchanged: - fmt.Println/Printf for CLI user output (version, banners, import results) - engine.AddLog for TUI-visible ring buffer (monitoring events) Store migration diagnostics demoted to slog.Debug (silent at default info level). HTTP request logging now structured with method/path/ status/duration/ip attributes.
This commit was merged in pull request #110.
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
@@ -47,7 +47,7 @@ func RunProbe(ctx context.Context, cfg ProbeConfig) error {
|
||||
}
|
||||
|
||||
if err := probeRegister(ctx, apiClient, cfg); err != nil {
|
||||
log.Printf("Probe: initial registration failed: %v (will retry)", err)
|
||||
slog.Error("probe initial registration failed", "err", err)
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -59,7 +59,7 @@ func RunProbe(ctx context.Context, cfg ProbeConfig) error {
|
||||
|
||||
sites, err := probeFetchAssignments(ctx, apiClient, cfg)
|
||||
if err != nil {
|
||||
log.Printf("Probe: failed to fetch assignments: %v", err)
|
||||
slog.Error("probe failed to fetch assignments", "err", err)
|
||||
sleepCtx(ctx, 10*time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func RunProbe(ctx context.Context, cfg ProbeConfig) error {
|
||||
|
||||
if len(results) > 0 {
|
||||
if err := probeReportResults(ctx, apiClient, cfg, results); err != nil {
|
||||
log.Printf("Probe: failed to report results: %v", err)
|
||||
slog.Error("probe failed to report results", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func probeReportResults(ctx context.Context, client *http.Client, cfg ProbeConfi
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("results returned %d", resp.StatusCode)
|
||||
}
|
||||
fmt.Printf("Probe: reported %d check results\n", len(results))
|
||||
slog.Info("probe reported check results", "count", len(results))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
@@ -64,11 +64,11 @@ func Start(cfg ServerConfig, s store.Store, eng *monitor.Engine) *http.Server {
|
||||
|
||||
func (s *Server) Start() *http.Server {
|
||||
if s.cfg.ClusterKey == "" {
|
||||
fmt.Println("WARNING: No UPTOP_CLUSTER_SECRET set. Cluster API endpoints are unauthenticated.")
|
||||
slog.Warn("no UPTOP_CLUSTER_SECRET set, cluster API endpoints are unauthenticated")
|
||||
}
|
||||
|
||||
if s.cfg.ClusterMode != "" && s.cfg.ClusterMode != "leader" && s.cfg.TLSCert == "" {
|
||||
fmt.Println("WARNING: Cluster mode active without TLS. Secrets transmitted in cleartext.")
|
||||
slog.Warn("cluster mode active without TLS, secrets transmitted in cleartext")
|
||||
}
|
||||
|
||||
handler := s.routes()
|
||||
@@ -84,14 +84,14 @@ func (s *Server) Start() *http.Server {
|
||||
}
|
||||
go func() {
|
||||
if s.cfg.TLSCert != "" && s.cfg.TLSKey != "" {
|
||||
fmt.Printf("HTTPS Server listening on %s\n", addr)
|
||||
slog.Info("HTTPS server listening", "addr", addr)
|
||||
if err := httpSrv.ListenAndServeTLS(s.cfg.TLSCert, s.cfg.TLSKey); err != nil && err != http.ErrServerClosed {
|
||||
log.Printf("HTTPS server error: %v", err)
|
||||
slog.Error("HTTPS server failed", "err", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("HTTP Server listening on %s\n", addr)
|
||||
slog.Info("HTTP server listening", "addr", addr)
|
||||
if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Printf("HTTP server error: %v", err)
|
||||
slog.Error("HTTP server failed", "err", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -139,7 +139,7 @@ func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) {
|
||||
if token == "" {
|
||||
if qt := r.URL.Query().Get("token"); qt != "" {
|
||||
token = qt
|
||||
log.Printf("DEPRECATED: push token in query string — use Authorization: Bearer header instead")
|
||||
slog.Warn("push token in query string is deprecated, use Authorization: Bearer header")
|
||||
}
|
||||
}
|
||||
if token == "" {
|
||||
@@ -174,7 +174,7 @@ func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
data, err := s.store.ExportData(r.Context())
|
||||
if err != nil {
|
||||
log.Printf("Export failed: %v", err)
|
||||
slog.Error("export failed", "err", err)
|
||||
http.Error(w, "Export failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -202,7 +202,7 @@ func (s *Server) handleImport(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if err := s.store.ImportData(r.Context(), data); err != nil {
|
||||
log.Printf("Import failed: %v", err)
|
||||
slog.Error("import failed", "err", err)
|
||||
http.Error(w, "Import failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -221,13 +221,13 @@ func (s *Server) handleKumaImport(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestBody)
|
||||
var kb importer.KumaBackup
|
||||
if err := json.NewDecoder(r.Body).Decode(&kb); err != nil {
|
||||
log.Printf("Invalid Kuma JSON: %v", err)
|
||||
slog.Error("invalid Kuma JSON", "err", err)
|
||||
http.Error(w, "Invalid Kuma JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
backup := importer.ConvertKuma(&kb)
|
||||
if err := s.store.ImportData(r.Context(), backup); err != nil {
|
||||
log.Printf("Kuma import failed: %v", err)
|
||||
slog.Error("Kuma import failed", "err", err)
|
||||
http.Error(w, "Import failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -261,7 +261,7 @@ func (s *Server) handleProbeRegister(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.store.RegisterNode(r.Context(), models.ProbeNode{
|
||||
ID: req.ID, Name: req.Name, Region: req.Region, Version: req.Version,
|
||||
}); err != nil {
|
||||
log.Printf("Probe register failed: %v", err)
|
||||
slog.Error("probe registration failed", "err", err)
|
||||
http.Error(w, "Registration failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -340,7 +340,7 @@ func (s *Server) handleProbeResults(w http.ResponseWriter, r *http.Request) {
|
||||
s.eng.IngestProbeResult(req.NodeID, result.SiteID, result.LatencyNs, result.IsUp, result.ErrorReason)
|
||||
}
|
||||
if err := s.store.UpdateNodeLastSeen(r.Context(), req.NodeID); err != nil {
|
||||
log.Printf("Failed to update node last seen: %v", err)
|
||||
slog.Error("node last-seen update failed", "err", err)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]bool{"ok": true}) //nolint:errcheck
|
||||
}
|
||||
@@ -444,7 +444,7 @@ func loggingMiddleware(trusted []*net.IPNet, next http.Handler) http.Handler {
|
||||
sw := &statusWriter{ResponseWriter: w, code: 200}
|
||||
next.ServeHTTP(sw, r)
|
||||
path := strings.ReplaceAll(strings.ReplaceAll(r.URL.Path, "\n", ""), "\r", "")
|
||||
log.Printf("%s %s %d %s %s", r.Method, path, sw.code, time.Since(start).Round(time.Millisecond), clientIP(r, trusted)) //nolint:gosec // path sanitized above
|
||||
slog.Info("http request", "method", r.Method, "path", path, "status", sw.code, "duration", time.Since(start).Round(time.Millisecond), "ip", clientIP(r, trusted)) //nolint:gosec // structured slog, not format string
|
||||
})
|
||||
}
|
||||
|
||||
@@ -485,7 +485,7 @@ func renderStatusPage(w http.ResponseWriter, title string, eng *monitor.Engine)
|
||||
Sites []models.Site
|
||||
}{Title: title, Sites: sites}
|
||||
if err := statusTpl.Execute(w, data); err != nil {
|
||||
log.Printf("Failed to render status page: %v", err)
|
||||
slog.Error("status page render failed", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -2,7 +2,7 @@ package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"log/slog"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
@@ -133,39 +133,39 @@ func (d *PostgresDialect) ResetSequenceOnEmpty(db *sql.DB, table string) {}
|
||||
|
||||
func (d *PostgresDialect) ImportWipe(tx *sql.Tx) {
|
||||
if _, err := tx.Exec("TRUNCATE TABLE sites RESTART IDENTITY CASCADE"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "sites", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("TRUNCATE TABLE alerts RESTART IDENTITY CASCADE"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "alerts", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("TRUNCATE TABLE users RESTART IDENTITY CASCADE"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "users", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("TRUNCATE TABLE maintenance_windows RESTART IDENTITY CASCADE"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "maintenance_windows", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("TRUNCATE TABLE check_history RESTART IDENTITY CASCADE"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "check_history", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("TRUNCATE TABLE state_changes RESTART IDENTITY CASCADE"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "state_changes", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("TRUNCATE TABLE alert_health RESTART IDENTITY CASCADE"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "alert_health", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *PostgresDialect) ImportResetSequences(tx *sql.Tx) {
|
||||
if _, err := tx.Exec("SELECT setval('sites_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sites))"); err != nil {
|
||||
log.Printf("sequence reset error: %v", err)
|
||||
slog.Debug("sequence reset failed", "table", "sites", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("SELECT setval('alerts_id_seq', (SELECT COALESCE(MAX(id), 1) FROM alerts))"); err != nil {
|
||||
log.Printf("sequence reset error: %v", err)
|
||||
slog.Debug("sequence reset failed", "table", "alerts", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("SELECT setval('users_id_seq', (SELECT COALESCE(MAX(id), 1) FROM users))"); err != nil {
|
||||
log.Printf("sequence reset error: %v", err)
|
||||
slog.Debug("sequence reset failed", "table", "users", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("SELECT setval('maintenance_windows_id_seq', (SELECT COALESCE(MAX(id), 1) FROM maintenance_windows))"); err != nil {
|
||||
log.Printf("sequence reset error: %v", err)
|
||||
slog.Debug("sequence reset failed", "table", "maintenance_windows", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -3,7 +3,7 @@ package store
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
@@ -141,44 +141,44 @@ func (d *SQLiteDialect) ResetSequenceOnEmpty(db *sql.DB, table string) {
|
||||
_ = db.QueryRow("SELECT COUNT(*) FROM " + table).Scan(&count) //nolint:errcheck
|
||||
if count == 0 {
|
||||
if _, err := db.Exec("DELETE FROM sqlite_sequence WHERE name=?", table); err != nil {
|
||||
log.Printf("sequence cleanup error: %v", err)
|
||||
slog.Debug("sequence cleanup failed", "table", table, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) ImportWipe(tx *sql.Tx) {
|
||||
if _, err := tx.Exec("DELETE FROM sites"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "sites", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='sites'"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "sqlite_sequence(sites)", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM alerts"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "alerts", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='alerts'"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "sqlite_sequence(alerts)", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM users"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "users", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='users'"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "sqlite_sequence(users)", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM maintenance_windows"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "maintenance_windows", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='maintenance_windows'"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "sqlite_sequence(maintenance_windows)", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM check_history"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "check_history", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM state_changes"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "state_changes", "err", err)
|
||||
}
|
||||
if _, err := tx.Exec("DELETE FROM alert_health"); err != nil {
|
||||
log.Printf("import wipe error: %v", err)
|
||||
slog.Debug("import wipe failed", "table", "alert_health", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user