From 13f5a0aa1c3e64d4768348c3a4f2b1b5f67a812a Mon Sep 17 00:00:00 2001 From: m Date: Mon, 29 Jun 2026 17:26:38 +0200 Subject: [PATCH] fix: use slog instead of log to specify stdout/stderr for different log levels --- cmd/certpicker/main.go | 17 ++-- cmd/certstream-server-go/createIndex.go | 4 +- cmd/certstream-server-go/main.go | 4 +- cmd/certstream-server-go/root.go | 4 +- cmd/certstream-server-go/validate.go | 6 +- internal/certificatetransparency/ct-parser.go | 8 +- internal/certificatetransparency/ct-tiled.go | 8 +- .../certificatetransparency/ct-watcher.go | 86 +++++++++---------- internal/certstream/certstream.go | 15 ++-- internal/config/config.go | 41 ++++----- internal/config/config_test.go | 2 +- internal/logger/logger.go | 70 +++++++++++++++ internal/metrics/logmetrics.go | 36 ++++---- internal/metrics/prometheus.go | 6 +- internal/models/certstream.go | 6 +- internal/web/broadcastmanager.go | 8 +- internal/web/client.go | 20 ++--- internal/web/server.go | 30 +++---- 18 files changed, 223 insertions(+), 148 deletions(-) create mode 100644 internal/logger/logger.go diff --git a/cmd/certpicker/main.go b/cmd/certpicker/main.go index cc82bf5..e14cfef 100644 --- a/cmd/certpicker/main.go +++ b/cmd/certpicker/main.go @@ -5,13 +5,14 @@ import ( "encoding/json" "flag" "fmt" - "log" + "log/slog" "net/http" "strings" "time" "github.com/d-Rickyy-b/certstream-server-go/internal/certificatetransparency" "github.com/d-Rickyy-b/certstream-server-go/internal/config" + "github.com/d-Rickyy-b/certstream-server-go/internal/logger" "github.com/d-Rickyy-b/certstream-server-go/internal/models" ct "github.com/google/certificate-transparency-go" @@ -23,6 +24,8 @@ import ( var userAgent = fmt.Sprintf("Certstream v%s (github.com/d-Rickyy-b/certstream-server-go)", config.Version) func main() { + logger.Init() + ctLogFlag := flag.String("log", "", "URL of the CT log - e.g. ct.googleapis.com/logs/eu1/xenon2025h2") certIDFlag := flag.Int64("cert", 0, "ID of the certificate to fetch from the CT log") chainFlag := flag.Bool("chain", false, "Include full chain for the certificate") @@ -34,7 +37,7 @@ func main() { certID := *certIDFlag if ctLog == "" { - log.Fatalln("CT log URL is required") + logger.Fatal("CT log URL is required") } if !strings.HasPrefix(ctLog, "https://") { @@ -57,29 +60,29 @@ func main() { jsonClient, e := client.New(ctLog, &httpClient, jsonclient.Options{UserAgent: userAgent}) if e != nil { - log.Fatalln("Error creating JSON client:", e) + logger.Fatal("Error creating JSON client", "error", e) } entries, getEntryErr := getEntry(jsonClient, certID) if getEntryErr != nil { - log.Fatalln("Error getting entry from CT log: ", getEntryErr) + logger.Fatal("Error getting entry from CT log", "error", getEntryErr) } // Loop over entries and pars each one. for _, leafEntry := range entries.Entries { rawLogEntry, err := ct.RawLogEntryFromLeaf(certID, &leafEntry) if err != nil { - log.Fatalln("Error creating raw log entry: ", err) + logger.Fatal("Error creating raw log entry", "error", err) } entry, parseErr := certificatetransparency.ParseCertstreamEntry(rawLogEntry, "N/A", "N/A", ctLog, models.SourceIsRFC6962) if parseErr != nil { - log.Fatalln("Error parsing certstream entry: ", parseErr) + logger.Fatal("Error parsing certstream entry", "error", parseErr) } // Check if the entry is a certificate or precertificate if logEntry, toLogEntryErr := rawLogEntry.ToLogEntry(); toLogEntryErr != nil { - log.Println("Error converting rawLogEntry to logEntry: ", toLogEntryErr) + slog.Error("Error converting rawLogEntry to logEntry", "error", toLogEntryErr) } else { matcher := scanner.MatchAll{} if logEntry.X509Cert != nil && matcher.CertificateMatches(logEntry.X509Cert) { diff --git a/cmd/certstream-server-go/createIndex.go b/cmd/certstream-server-go/createIndex.go index 6c9de7f..83b5a6b 100644 --- a/cmd/certstream-server-go/createIndex.go +++ b/cmd/certstream-server-go/createIndex.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "log" "os" "path/filepath" @@ -10,6 +9,7 @@ import ( "github.com/d-Rickyy-b/certstream-server-go/internal/certstream" "github.com/d-Rickyy-b/certstream-server-go/internal/config" + "github.com/d-Rickyy-b/certstream-server-go/internal/logger" ) // createIndexCmd represents the createIndex command. @@ -62,7 +62,7 @@ create-index will create and pre fill the ct-index.json file with the current va createErr := certstreamServer.CreateIndexFile(outFilePath) if createErr != nil { - log.Fatalf("Error while creating index file: %v", createErr) + logger.Fatal("Error while creating index file", "error", createErr) } return nil diff --git a/cmd/certstream-server-go/main.go b/cmd/certstream-server-go/main.go index 9cc7d9b..7e7c8fc 100644 --- a/cmd/certstream-server-go/main.go +++ b/cmd/certstream-server-go/main.go @@ -1,11 +1,11 @@ package main import ( - "log" + "github.com/d-Rickyy-b/certstream-server-go/internal/logger" ) // main is the entry point for the application. func main() { - log.SetFlags(log.LstdFlags | log.Lshortfile) + logger.Init() Execute() } diff --git a/cmd/certstream-server-go/root.go b/cmd/certstream-server-go/root.go index a5f63c9..fa1405d 100644 --- a/cmd/certstream-server-go/root.go +++ b/cmd/certstream-server-go/root.go @@ -2,13 +2,13 @@ package main import ( "fmt" - "log" "os" "github.com/spf13/cobra" "github.com/d-Rickyy-b/certstream-server-go/internal/certstream" "github.com/d-Rickyy-b/certstream-server-go/internal/config" + "github.com/d-Rickyy-b/certstream-server-go/internal/logger" ) // rootCmd represents the base command when called without any subcommands. @@ -44,7 +44,7 @@ certificate transparency logs via websocket connections to connected clients.`, certstreamServer, err := certstream.NewCertstreamFromConfigFile(configPath) if err != nil { - log.Fatalf("Error while creating certstream server: %v", err) + logger.Fatal("Error while creating certstream server", "error", err) } certstreamServer.Start() diff --git a/cmd/certstream-server-go/validate.go b/cmd/certstream-server-go/validate.go index 991874f..463c9dd 100644 --- a/cmd/certstream-server-go/validate.go +++ b/cmd/certstream-server-go/validate.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "log" + "log/slog" "os" "github.com/spf13/cobra" @@ -40,10 +40,10 @@ This command deserializes the config and checks for errors.`, readConfErr := config.ValidateConfig(configPath) if readConfErr != nil { - log.Fatalln(readConfErr) + return readConfErr } - log.Println("Config file is valid!") + slog.Info("Config file is valid") return nil }, diff --git a/internal/certificatetransparency/ct-parser.go b/internal/certificatetransparency/ct-parser.go index 2bed1fa..df1941e 100644 --- a/internal/certificatetransparency/ct-parser.go +++ b/internal/certificatetransparency/ct-parser.go @@ -8,7 +8,7 @@ import ( "encoding/hex" "fmt" "hash" - "log" + "log/slog" "math/big" "slices" "strings" @@ -48,7 +48,7 @@ func parseData(entry *ct.RawLogEntry, operatorName, logName, ctURL, logType stri // Convert RawLogEntry to ct.LogEntry logEntry, conversionErr := entry.ToLogEntry() if conversionErr != nil { - log.Println("Could not convert entry to LogEntry: ", conversionErr) + slog.Error("Could not convert entry to LogEntry", "error", conversionErr) return models.Data{}, fmt.Errorf("could not convert entry to logentry: %w", conversionErr) } @@ -92,7 +92,7 @@ func parseData(entry *ct.RawLogEntry, operatorName, logName, ctURL, logType stri chain, parseErr := parseCertificateChain(logEntry) if parseErr != nil { - log.Println("Could not parse certificate chain: ", parseErr) + slog.Error("Could not parse certificate chain", "error", parseErr) return models.Data{}, parseErr } @@ -300,7 +300,7 @@ func parseName(input []string) *string { func calculateHash(data []byte, certHasher hash.Hash) string { _, e := certHasher.Write(data) if e != nil { - log.Printf("Error while hashing cert: %s\n", e) + slog.Error("Error while hashing cert", "error", e) return "" } diff --git a/internal/certificatetransparency/ct-tiled.go b/internal/certificatetransparency/ct-tiled.go index 52f3dca..9c06be0 100644 --- a/internal/certificatetransparency/ct-tiled.go +++ b/internal/certificatetransparency/ct-tiled.go @@ -5,7 +5,7 @@ import ( "context" "fmt" "io" - "log" + "log/slog" "net/http" "strconv" "strings" @@ -252,7 +252,7 @@ func (s *StaticCTClient) Monitor(ctx context.Context, foundCert func(*ct.RawLogE for { hadNewEntries, err := s.fetchAndProcessTiles(ctx, foundCert, foundPrecert) if err != nil { - log.Printf("Error processing tiled log updates for '%s': %s\n", s.url, err) + slog.Error("Error processing tiled log updates", "url", s.url, "error", err) return err } @@ -305,7 +305,7 @@ func (s *StaticCTClient) fetchAndProcessTiles(ctx context.Context, foundCert fun partialSize := currentTreeSize % TileSize if partialSize > 0 { if err := s.processTile(ctx, endTile, partialSize, foundCert, foundPrecert); err != nil { - log.Printf("Warning: error processing partial tile %d: %s\n", endTile, err) + slog.Warn("Error processing partial tile", "tile", endTile, "error", err) // Don't return error for partial tiles as they might be incomplete } } @@ -342,7 +342,7 @@ func (s *StaticCTClient) processTile(ctx context.Context, tileIndex, partialWidt case EntryTypePrecert: foundPrecert(rawEntry) default: - log.Printf("Unknown entry type %d in tile %d, skipping entry at index %d\n", leaf.EntryType, tileIndex, entryIndex) + slog.Warn("Unknown entry type in tile, skipping entry", "entry_type", leaf.EntryType, "tile", tileIndex, "index", entryIndex) } // Update the index diff --git a/internal/certificatetransparency/ct-watcher.go b/internal/certificatetransparency/ct-watcher.go index 75b5420..fd89569 100644 --- a/internal/certificatetransparency/ct-watcher.go +++ b/internal/certificatetransparency/ct-watcher.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "io" - "log" + "log/slog" "net/http" "path/filepath" "strings" @@ -66,7 +66,7 @@ func (w *Watcher) Start() { if config.AppConfig.General.Recovery.Enabled { ctIndexFilePath, err := filepath.Abs(config.AppConfig.General.Recovery.CTIndexFile) if err != nil { - log.Printf("Error getting absolute path for CT index file: '%s', %s\n", config.AppConfig.General.Recovery.CTIndexFile, err) + slog.Error("Error getting absolute path for CT index file", "path", config.AppConfig.General.Recovery.CTIndexFile, "error", err) return } @@ -81,7 +81,7 @@ func (w *Watcher) Start() { // initialize the watcher with currently available logs w.updateLogs() - log.Println("Started CT watcher") + slog.Info("Started CT watcher") go certHandler(w.certChan) go w.watchNewLogs() @@ -113,11 +113,11 @@ func (w *Watcher) updateLogs() { // Get a list of urls of all CT logs provided by Google logList, err := getAllLogs(googleLogListFetcher) if err != nil { - log.Println(err) + slog.Error("Failed to get log list", "error", err) return } - log.Println("Checking for new ct logs...") + slog.Info("Checking for new CT logs") // Track all URLs that should be monitored after reconciliation monitoredURLs := make(map[string]struct{}) @@ -134,7 +134,7 @@ func (w *Watcher) updateLogs() { normURL := normalizeCtlogURL(url) if transparencyLog.State.LogStatus() == loglist3.RetiredLogStatus { - log.Printf("Skipping retired CT log: %s\n", normURL) + slog.Info("Skipping retired CT log", "url", normURL) continue } @@ -152,7 +152,7 @@ func (w *Watcher) updateLogs() { normURL := normalizeCtlogURL(url) if transparencyLog.State.LogStatus() == loglist3.RetiredLogStatus { - log.Printf("Skipping retired CT log: %s\n", normURL) + slog.Info("Skipping retired CT log", "url", normURL) continue } @@ -164,7 +164,7 @@ func (w *Watcher) updateLogs() { } } - log.Printf("New ct logs found: %d\n", newCTs) + slog.Info("New CT logs found", "count", newCTs) // Optionally stop workers for logs not in the monitoredURLs set if *config.AppConfig.General.DropOldLogs { @@ -173,17 +173,17 @@ func (w *Watcher) updateLogs() { for _, ctWorker := range w.workers { normURL := normalizeCtlogURL(ctWorker.ctURL) if _, ok := monitoredURLs[normURL]; !ok { - log.Printf("Stopping worker. CT URL not found in LogList or retired: '%s'\n", ctWorker.ctURL) + slog.Info("Stopping worker, CT URL not found in log list or retired", "url", ctWorker.ctURL) ctWorker.stop() removed++ } } - log.Printf("Removed ct logs: %d\n", removed) + slog.Info("Removed CT logs", "count", removed) } - log.Printf("Currently monitored ct logs: %d\n", len(w.workers)) + slog.Info("Currently monitored CT logs", "count", len(w.workers)) } // addLogIfNew checks if a log is already being watched and adds it if not. @@ -229,7 +229,7 @@ func (w *Watcher) addLogIfNew(operatorName, description, url string, isTiled boo // discardWorker removes a worker from the watcher's list of workers. // This needs to be done when a worker stops. func (w *Watcher) discardWorker(worker *worker) { - log.Println("Removing worker for CT log:", worker.ctURL) + slog.Info("Removing worker for CT log", "url", worker.ctURL) w.workersMu.Lock() defer w.workersMu.Unlock() @@ -244,7 +244,7 @@ func (w *Watcher) discardWorker(worker *worker) { // Stop stops the watcher. func (w *Watcher) Stop() { - log.Printf("Stopping watcher\n") + slog.Info("Stopping watcher") if config.AppConfig.General.Recovery.Enabled { // Store current CT Indexes before shutting down @@ -252,7 +252,7 @@ func (w *Watcher) Stop() { err := metrics.Metrics.SaveCertIndexes(filePath) if err != nil { - log.Printf("Failed to save CT index file: %v\n", err) + slog.Error("Failed to save CT index file", "error", err) } } @@ -269,30 +269,30 @@ func (w *Watcher) CreateIndexFile(filePath string) error { httpClient := newHTTPClient() w.context, w.cancelFunc = context.WithCancel(context.Background()) - log.Println("Fetching current STH for all logs...") + slog.Info("Fetching current STH for all logs") for _, operator := range logs.Operators { // Iterate over each log of the operator for _, transparencyLog := range operator.Logs { if transparencyLog.State.LogStatus() == loglist3.RetiredLogStatus { - log.Printf("Skipping retired CT log: %s\n", transparencyLog.URL) + slog.Info("Skipping retired CT log", "url", transparencyLog.URL) continue } normalizedURL := normalizeCtlogURL(transparencyLog.URL) metrics.Metrics.Init(operator.Name, normalizedURL) - log.Println("Fetching STH for", normalizedURL) + slog.Info("Fetching STH", "url", normalizedURL) jsonClient, e := client.New(transparencyLog.URL, httpClient, jsonclient.Options{UserAgent: UserAgent}) if e != nil { - log.Printf("Error creating JSON client: %s\n", e) + slog.Error("Error creating JSON client", "error", e) continue } sth, getSTHerr := jsonClient.GetSTH(w.context) if getSTHerr != nil { // TODO this can happen due to a 429 error. We should retry the request - log.Printf("Could not get STH for '%s': %s\n", transparencyLog.URL, getSTHerr) + slog.Error("Could not get STH", "url", transparencyLog.URL, "error", getSTHerr) continue } @@ -301,18 +301,18 @@ func (w *Watcher) CreateIndexFile(filePath string) error { for _, transparencyLog := range operator.TiledLogs { if transparencyLog.State.LogStatus() == loglist3.RetiredLogStatus { - log.Printf("Skipping retired CT log: %s\n", transparencyLog.MonitoringURL) + slog.Info("Skipping retired CT log", "url", transparencyLog.MonitoringURL) continue } normalizedURL := normalizeCtlogURL(transparencyLog.MonitoringURL) metrics.Metrics.Init(operator.Name, normalizedURL) - log.Println("Fetching checkpoint for", normalizedURL) + slog.Info("Fetching checkpoint", "url", normalizedURL) staticCTClient := NewStaticCTClient(transparencyLog.MonitoringURL, httpClient, UserAgent, 0) checkpoint, fetchErr := staticCTClient.FetchCheckpoint(w.context) if fetchErr != nil { - log.Printf("Could not get checkpoint for '%s': %s\n", transparencyLog.MonitoringURL, fetchErr) + slog.Error("Could not get checkpoint", "url", transparencyLog.MonitoringURL, "error", fetchErr) return ErrFetchingSTHFailed } @@ -327,7 +327,7 @@ func (w *Watcher) CreateIndexFile(filePath string) error { return saveErr } - log.Println("Index file saved to", filePath) + slog.Info("Index file saved", "path", filePath) return nil } @@ -355,12 +355,12 @@ func (w *worker) startDownloadingCerts(ctx context.Context) { w.ctURL = "https://" + w.ctURL } - log.Printf("Initializing worker for CT log: %s\n", w.ctURL) - defer log.Printf("Stopping worker for CT log: %s\n", w.ctURL) + slog.Info("Initializing worker for CT log", "url", w.ctURL) + defer slog.Info("Stopping worker for CT log", "url", w.ctURL) w.mu.Lock() if w.running { - log.Printf("Worker for '%s' already running\n", w.ctURL) + slog.Warn("Worker already running", "url", w.ctURL) w.mu.Unlock() return @@ -371,7 +371,7 @@ func (w *worker) startDownloadingCerts(ctx context.Context) { w.mu.Unlock() for { - log.Printf("Starting worker for CT log: %s\n", w.ctURL) + slog.Info("Starting worker for CT log", "url", w.ctURL) var workerErr error if w.isTiled { @@ -383,32 +383,32 @@ func (w *worker) startDownloadingCerts(ctx context.Context) { if workerErr != nil { switch { case errors.Is(workerErr, ErrFetchingSTHFailed): - log.Printf("Worker for '%s' failed - could not fetch STH\n", w.ctURL) + slog.Error("Worker failed, could not fetch STH", "url", w.ctURL) return case errors.Is(workerErr, ErrCreatingClient): - log.Printf("Worker for '%s' failed - could not create client\n", w.ctURL) + slog.Error("Worker failed, could not create client", "url", w.ctURL) return case strings.Contains(workerErr.Error(), "no such host"): - log.Printf("Worker for '%s' failed to resolve host: %s\n", w.ctURL, workerErr) + slog.Error("Worker failed to resolve host", "url", w.ctURL, "error", workerErr) return case errors.Is(workerErr, context.Canceled): - log.Printf("Worker for '%s' canceled\n", w.ctURL) + slog.Info("Worker canceled", "url", w.ctURL) return } - log.Printf("Worker for '%s' failed with unexpected error: %s\n", w.ctURL, workerErr) + slog.Error("Worker failed with unexpected error", "url", w.ctURL, "error", workerErr) } // Check if the context was canceled select { case <-ctx.Done(): - log.Printf("Context was cancelled; Stopping worker for '%s'\n", w.ctURL) + slog.Info("Context canceled, stopping worker", "url", w.ctURL) return default: - log.Printf("Worker for '%s' sleeping for 5 seconds due to error\n", w.ctURL) + slog.Warn("Worker sleeping due to error, will restart", "url", w.ctURL) time.Sleep(5 * time.Second) - log.Printf("Restarting worker for '%s'\n", w.ctURL) + slog.Info("Restarting worker", "url", w.ctURL) continue } @@ -426,7 +426,7 @@ func (w *worker) stop() { func (w *worker) runStandardWorker(ctx context.Context) error { jsonClient, e := client.New(w.ctURL, newHTTPClient(), jsonclient.Options{UserAgent: UserAgent}) if e != nil { - log.Printf("Error creating JSON client: %s\n", e) + slog.Error("Error creating JSON client", "error", e) return ErrCreatingClient } @@ -436,7 +436,7 @@ func (w *worker) runStandardWorker(ctx context.Context) error { sth, getSTHerr := jsonClient.GetSTH(ctx) if getSTHerr != nil { // TODO this can happen due to a 429 error. We should retry the request - log.Printf("Could not get STH for '%s': %s\n", w.ctURL, getSTHerr) + slog.Error("Could not get STH", "url", w.ctURL, "error", getSTHerr) return ErrFetchingSTHFailed } // Start at the latest STH to skip all the past certificates @@ -461,7 +461,7 @@ func (w *worker) runStandardWorker(ctx context.Context) error { return fmt.Errorf("error scanning for certificates: %w", scanErr) } - log.Printf("Exiting worker %s without error!\n", w.ctURL) + slog.Info("Exiting worker without error", "url", w.ctURL) return nil } @@ -477,7 +477,7 @@ func (w *worker) runTiledWorker(ctx context.Context) error { if !validSavedCTIndexExists { checkpoint, err := staticCTClient.FetchCheckpoint(ctx) if err != nil { - log.Printf("Could not get checkpoint for '%s': %s\n", w.ctURL, err) + slog.Error("Could not get checkpoint", "url", w.ctURL, "error", err) return ErrFetchingSTHFailed } // Start at the latest checkpoint to skip all the past certificates @@ -501,7 +501,7 @@ func (w *worker) foundCertCallback(rawEntry *ct.RawLogEntry) { entry, parseErr := ParseCertstreamEntry(rawEntry, w.operatorName, w.name, w.ctURL, logType) if parseErr != nil { - log.Println("Error parsing certstream entry: ", parseErr) + slog.Error("Error parsing certstream entry", "error", parseErr) return } @@ -520,7 +520,7 @@ func (w *worker) foundPrecertCallback(rawEntry *ct.RawLogEntry) { entry, parseErr := ParseCertstreamEntry(rawEntry, w.operatorName, w.name, w.ctURL, logType) if parseErr != nil { - log.Println("Error parsing certstream entry: ", parseErr) + slog.Error("Error parsing certstream entry", "error", parseErr) return } @@ -540,7 +540,7 @@ func certHandler(entryChan chan models.Entry) { processed++ if processed%1000 == 0 { - log.Printf("Processed %d entries | Queue length: %d\n", processed, len(entryChan)) + slog.Info("Processed entries", "processed", processed, "queue_length", len(entryChan)) // Every thousandth entry, we store one certificate as example web.SetExampleCert(entry) } @@ -608,7 +608,7 @@ func getAllLogs(logListFetcher LogListFetcher) (loglist3.LogList, error) { allLogs, err = logListFetcher() if err != nil { - log.Printf("Error fetching log list from Google: %s\n", err) + slog.Error("Error fetching log list from Google", "error", err) return loglist3.LogList{}, fmt.Errorf("failed to fetch log list from Google: %w", err) } } diff --git a/internal/certstream/certstream.go b/internal/certstream/certstream.go index 3bf4065..b27e2b1 100644 --- a/internal/certstream/certstream.go +++ b/internal/certstream/certstream.go @@ -6,13 +6,14 @@ package certstream import ( "fmt" - "log" + "log/slog" "os" "os/signal" "syscall" "github.com/d-Rickyy-b/certstream-server-go/internal/certificatetransparency" "github.com/d-Rickyy-b/certstream-server-go/internal/config" + "github.com/d-Rickyy-b/certstream-server-go/internal/logger" "github.com/d-Rickyy-b/certstream-server-go/internal/metrics" "github.com/d-Rickyy-b/certstream-server-go/internal/web" ) @@ -67,10 +68,10 @@ func (cs *Certstream) setupMetrics(webserver *web.Server) { // If prometheus is enabled, and interface is either unconfigured or same as webserver config, use existing webserver if (cs.config.Prometheus.ListenAddr == "" || cs.config.Prometheus.ListenAddr == cs.config.Webserver.ListenAddr) && (cs.config.Prometheus.ListenPort == 0 || cs.config.Prometheus.ListenPort == cs.config.Webserver.ListenPort) { - log.Println("Starting prometheus server on same interface as webserver") + slog.Info("Starting prometheus server on same interface as webserver") webserver.RegisterPrometheus(cs.config.Prometheus.MetricsURL, metrics.Prometheus.Write) } else { - log.Println("Starting prometheus server on new interface") + slog.Info("Starting prometheus server on new interface") cs.metricsServer = web.NewMetricsServer( cs.config.Prometheus.ListenAddr, @@ -86,7 +87,7 @@ func (cs *Certstream) setupMetrics(webserver *web.Server) { // Start starts the webserver and the watcher. // This is a blocking function that will run until the server is stopped. func (cs *Certstream) Start() { - log.Printf("Starting certstream-server-go v%s\n", config.Version) + slog.Info("Starting certstream-server-go", "version", config.Version) // handle signals in a separate goroutine signals := make(chan os.Signal, 1) @@ -101,7 +102,7 @@ func (cs *Certstream) Start() { // Start webserver and metrics server if cs.webserver == nil { - log.Fatalln("Webserver not initialized! Exiting...") + logger.Fatal("Webserver not initialized, exiting") } go cs.webserver.Start() @@ -148,10 +149,10 @@ func (cs *Certstream) CreateIndexFile(outFile string) error { // signalHandler listens for signals in order to gracefully shut down the server. // Executes the callback function when a signal is received. func signalHandler(signals chan os.Signal, callback func()) { - log.Println("Listening for signals...") + slog.Info("Listening for signals...") sig := <-signals - log.Printf("Received signal %v. Shutting down...\n", sig) + slog.Info("Received signal, shutting down", "signal", sig) callback() os.Exit(0) } diff --git a/internal/config/config.go b/internal/config/config.go index 58245a3..4d4697c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,11 +3,12 @@ package config import ( "errors" "fmt" - "log" + "log/slog" "net" "regexp" "strings" + "github.com/d-Rickyy-b/certstream-server-go/internal/logger" "github.com/spf13/viper" ) @@ -132,12 +133,12 @@ func initViper(configPath string) *viper.Viper { if err := v.ReadInConfig(); err != nil { var notFound viper.ConfigFileNotFoundError if errors.As(err, ¬Found) { - log.Println("No config file found, using defaults and environment variables only") + slog.Info("No config file found, using defaults and environment variables only") } else { - log.Fatalf("Error reading config file: %v", err) + logger.Fatal("Error reading config file", "error", err) } } else { - log.Printf("Using config file: %s\n", v.ConfigFileUsed()) + slog.Info("Using config file", "path", v.ConfigFileUsed()) } // Environment variables @@ -177,35 +178,35 @@ func validateConfig(config *Config) bool { // Check webserver config if config.Webserver.ListenAddr == "" || net.ParseIP(config.Webserver.ListenAddr) == nil { - log.Fatalln("Webhook listen IP is not a valid IP: ", config.Webserver.ListenAddr) + logger.Fatal("Webhook listen IP is not a valid IP", "addr", config.Webserver.ListenAddr) return false } if config.Webserver.ListenPort == 0 { - log.Fatalln("Webhook listen port is not set") + logger.Fatal("Webhook listen port is not set") return false } if config.Webserver.FullURL == "" || !URLPathRegex.MatchString(config.Webserver.FullURL) { - log.Println("Webhook full URL is not set or does not match pattern '/...'") + slog.Warn("Webhook full URL is not set or does not match pattern '/...', using default", "url", config.Webserver.FullURL) config.Webserver.FullURL = "/full-stream" } if config.Webserver.LiteURL == "" || !URLPathRegex.MatchString(config.Webserver.FullURL) { - log.Println("Webhook lite URL is not set or does not match pattern '/...'") + slog.Warn("Webhook lite URL is not set or does not match pattern '/...', using default", "url", config.Webserver.LiteURL) config.Webserver.LiteURL = "/" } if config.Webserver.DomainsOnlyURL == "" || !URLPathRegex.MatchString(config.Webserver.DomainsOnlyURL) { - log.Println("Webhook domains only URL is not set or does not match pattern '/...'") + slog.Warn("Webhook domains only URL is not set or does not match pattern '/...', using default", "url", config.Webserver.DomainsOnlyURL) config.Webserver.FullURL = "/domains-only" } if config.Webserver.FullURL == config.Webserver.LiteURL { - log.Fatalln("Webhook full URL is the same as lite URL - please fix the config!") + logger.Fatal("Webhook full URL is the same as lite URL - please fix the config") } if config.Webserver.DomainsOnlyURL == "" { @@ -219,7 +220,7 @@ func validateConfig(config *Config) bool { _, _, err := net.ParseCIDR(ip) if err != nil { - log.Fatalln("Invalid IP/CIDR in webserver trusted_proxies: ", ip) + logger.Fatal("Invalid IP/CIDR in webserver trusted_proxies", "ip", ip) return false } } @@ -227,12 +228,12 @@ func validateConfig(config *Config) bool { //nolint:nestif if config.Prometheus.Enabled { if config.Prometheus.ListenAddr == "" || net.ParseIP(config.Prometheus.ListenAddr) == nil { - log.Fatalln("Metrics export IP is not a valid IP") + logger.Fatal("Metrics export IP is not a valid IP") return false } if config.Prometheus.ListenPort == 0 { - log.Fatalln("Metrics export port is not set") + logger.Fatal("Metrics export port is not set") return false } @@ -249,7 +250,7 @@ func validateConfig(config *Config) bool { // Provided entry is not an IP, check if it's a CIDR range _, _, err := net.ParseCIDR(ip) if err != nil { - log.Fatalln("Invalid IP in metrics whitelist: ", ip) + logger.Fatal("Invalid IP in metrics whitelist", "ip", ip) return false } } @@ -261,7 +262,7 @@ func validateConfig(config *Config) bool { _, _, err := net.ParseCIDR(ip) if err != nil { - log.Fatalln("Invalid IP/CIDR in prometheus trusted_proxies: ", ip) + logger.Fatal("Invalid IP/CIDR in prometheus trusted_proxies", "ip", ip) return false } } @@ -272,7 +273,7 @@ func validateConfig(config *Config) bool { if len(config.General.AdditionalLogs) > 0 { for _, ctLog := range config.General.AdditionalLogs { if !URLRegex.MatchString(ctLog.URL) { - log.Println("Ignoring invalid additional log URL: ", ctLog.URL) + slog.Warn("Ignoring invalid additional log URL", "url", ctLog.URL) continue } @@ -283,7 +284,7 @@ func validateConfig(config *Config) bool { if len(config.General.AdditionalTiledLogs) > 0 { for _, ctLog := range config.General.AdditionalTiledLogs { if !URLRegex.MatchString(ctLog.URL) { - log.Println("Ignoring invalid additional log URL: ", ctLog.URL) + slog.Warn("Ignoring invalid additional log URL", "url", ctLog.URL) continue } @@ -295,7 +296,7 @@ func validateConfig(config *Config) bool { config.General.AdditionalTiledLogs = validTiledLogs if len(config.General.AdditionalLogs) == 0 && len(config.General.AdditionalTiledLogs) == 0 && config.General.DisableDefaultLogs { - log.Fatalln("Default logs are disabled, but no additional logs are configured. Please add at least one log to the config or enable default logs.") + logger.Fatal("Default logs are disabled, but no additional logs are configured. Please add at least one log to the config or enable default logs.") } if config.General.BufferSizes.Websocket <= 0 { @@ -312,14 +313,14 @@ func validateConfig(config *Config) bool { // If the cleanup flag is not set, default to true if config.General.DropOldLogs == nil { - log.Println("drop_old_logs is not set, defaulting to true") + slog.Info("drop_old_logs is not set, defaulting to true") defaultCleanup := true config.General.DropOldLogs = &defaultCleanup } if config.General.Recovery.Enabled && config.General.Recovery.CTIndexFile == "" { - log.Println("Recovery enabled but no index file specified. Defaulting to ./ct_index.json") + slog.Info("Recovery enabled but no index file specified, defaulting to ./ct_index.json") config.General.Recovery.CTIndexFile = "./ct_index.json" } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f9c9107..4eae6d7 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -520,7 +520,7 @@ webserver: ` configPath := writeConfigFile(t, yaml) - // validateConfig calls log.Fatalln on invalid entries, which calls os.Exit. + // validateConfig calls logger.Fatal on invalid entries, which calls os.Exit. // We verify indirectly by ensuring ReadConfig succeeds when all entries are valid // and that invalid CIDR/IP combinations are rejected during normal validation. // A direct fatal-exit test would require subprocess execution; skip that here diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..16c7097 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,70 @@ +package logger + +import ( + "context" + "log/slog" + "os" + "path/filepath" + "strconv" +) + +// splitHandler routes info-level logs to stdout and warn/error logs to stderr. +type splitHandler struct { + stdout slog.Handler + stderr slog.Handler +} + +func (h *splitHandler) Enabled(ctx context.Context, level slog.Level) bool { + return h.stdout.Enabled(ctx, level) || h.stderr.Enabled(ctx, level) +} + +func (h *splitHandler) Handle(ctx context.Context, r slog.Record) error { + if r.Level >= slog.LevelWarn { + return h.stderr.Handle(ctx, r) + } + + return h.stdout.Handle(ctx, r) +} + +func (h *splitHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &splitHandler{ + stdout: h.stdout.WithAttrs(attrs), + stderr: h.stderr.WithAttrs(attrs), + } +} + +func (h *splitHandler) WithGroup(name string) slog.Handler { + return &splitHandler{ + stdout: h.stdout.WithGroup(name), + stderr: h.stderr.WithGroup(name), + } +} + +// Init configures the default slog logger with source locations (short file:line). +func Init() { + opts := &slog.HandlerOptions{ + AddSource: true, + Level: slog.LevelInfo, + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + if a.Key == slog.SourceKey { + if source, ok := a.Value.Any().(*slog.Source); ok { + a.Value = slog.StringValue(filepath.Base(source.File) + ":" + strconv.Itoa(source.Line)) + } + } + + return a + }, + } + + handler := &splitHandler{ + stdout: slog.NewTextHandler(os.Stdout, opts), + stderr: slog.NewTextHandler(os.Stderr, opts), + } + slog.SetDefault(slog.New(handler)) +} + +// Fatal logs an error and exits with status 1. +func Fatal(msg string, args ...any) { + slog.Error(msg, args...) + os.Exit(1) +} diff --git a/internal/metrics/logmetrics.go b/internal/metrics/logmetrics.go index 1638897..79de742 100644 --- a/internal/metrics/logmetrics.go +++ b/internal/metrics/logmetrics.go @@ -3,7 +3,7 @@ package metrics import ( "encoding/json" "fmt" - "log" + "log/slog" "maps" "os" "sync" @@ -169,13 +169,13 @@ func (m *LogMetrics) SetCTIndex(url string, index uint64) { m.mutex.Lock() defer m.mutex.Unlock() - log.Printf("Setting CT index for %s to %d\n", url, index) + slog.Info("Setting CT index", "url", url, "index", index) m.index[url] = index } // LoadCTIndex loads the last cert index processed for each CT url if it exists. func (m *LogMetrics) LoadCTIndex(ctIndexFilePath string) { - log.Println("Loading CT indexes from file: ", ctIndexFilePath) + slog.Info("Loading CT indexes from file", "path", ctIndexFilePath) m.mutex.Lock() defer m.mutex.Unlock() @@ -186,34 +186,33 @@ func (m *LogMetrics) LoadCTIndex(ctIndexFilePath string) { if os.IsNotExist(readErr) { err := m.createCTIndexFile(ctIndexFilePath) if err != nil { - log.Printf("Error creating CT index file: '%s'\n", ctIndexFilePath) - log.Panicln(err) + slog.Error("Error creating CT index file", "path", ctIndexFilePath, "error", err) + panic(err) } bytes = []byte("{}") } else { - // If the file exists, but we can't read it, log the error and panic - log.Panicln(readErr) + slog.Error("Error reading CT index file", "path", ctIndexFilePath, "error", readErr) + panic(readErr) } } jerr := json.Unmarshal(bytes, &m.index) if jerr != nil { - log.Printf("Error unmarshalling CT index file: '%s'\n", ctIndexFilePath) - log.Panicln(jerr) + slog.Error("Error unmarshalling CT index file", "path", ctIndexFilePath, "error", jerr) + panic(jerr) } - log.Println("Successfully loaded saved CT indexes") + slog.Info("Successfully loaded saved CT indexes") } func (m *LogMetrics) createCTIndexFile(ctIndexFilePath string) error { - log.Printf("Specified CT index file does not exist: '%s'\n", ctIndexFilePath) - log.Println("Creating CT index file now!") + slog.Info("CT index file does not exist, creating it", "path", ctIndexFilePath) file, createErr := os.Create(ctIndexFilePath) if createErr != nil { - log.Printf("Error creating CT index file: '%s'\n", ctIndexFilePath) - log.Panicln(createErr) + slog.Error("Error creating CT index file", "path", ctIndexFilePath, "error", createErr) + panic(createErr) } defer file.Close() @@ -228,8 +227,8 @@ func (m *LogMetrics) createCTIndexFile(ctIndexFilePath string) error { _, writeErr := file.Write(bytes) if writeErr != nil { - log.Printf("Error writing to CT index file: '%s'\n", ctIndexFilePath) - log.Panicln(writeErr) + slog.Error("Error writing to CT index file", "path", ctIndexFilePath, "error", writeErr) + panic(writeErr) } return nil @@ -245,7 +244,7 @@ func (m *LogMetrics) SaveCertIndexesAtInterval(interval time.Duration, ctIndexFi for range ticker.C { if err := m.SaveCertIndexes(ctIndexFilePath); err != nil { - log.Printf("Error saving CT indexes at '%s': %s\n", ctIndexFilePath, err) + slog.Error("Error saving CT indexes", "path", ctIndexFilePath, "error", err) } } } @@ -259,7 +258,8 @@ func (m *LogMetrics) SaveCertIndexes(ctIndexFilePath string) error { bytes, cerr := json.MarshalIndent(ctIndex, "", " ") if cerr != nil { - log.Panic(cerr) + slog.Error("Error marshalling CT indexes", "error", cerr) + panic(cerr) } // Store index data in a temp file diff --git a/internal/metrics/prometheus.go b/internal/metrics/prometheus.go index c846226..c0f177a 100644 --- a/internal/metrics/prometheus.go +++ b/internal/metrics/prometheus.go @@ -3,7 +3,7 @@ package metrics import ( "fmt" "io" - "log" + "log/slog" "sync" "time" @@ -99,13 +99,13 @@ func (pm *PrometheusExporter) getCertCountForLog(operatorName, logname string) i operatorMetrics, ok := pm.tempCertMetrics[operatorName] if !ok { - log.Printf("No metrics for operator \"%s\"", operatorName) + slog.Warn("No metrics for operator", "operator", operatorName) return 0 } count, ok := operatorMetrics[logname] if !ok { - log.Printf("No metrics for log \"%s\" of operator \"%s\"", logname, operatorName) + slog.Warn("No metrics for log", "log", logname, "operator", operatorName) return 0 } diff --git a/internal/models/certstream.go b/internal/models/certstream.go index 4d2e4f5..8b1275b 100644 --- a/internal/models/certstream.go +++ b/internal/models/certstream.go @@ -3,7 +3,7 @@ package models import ( "bytes" "encoding/json" - "log" + "log/slog" ) type Entry struct { @@ -68,7 +68,7 @@ func (e *Entry) JSONDomains() []byte { domainsEntryBytes, err := json.Marshal(domainsEntry) if err != nil { - log.Println(err) + slog.Error("Error marshalling domains entry", "error", err) } return domainsEntryBytes @@ -82,7 +82,7 @@ func (e *Entry) entryToJSONBytes() []byte { err := enc.Encode(e) if err != nil { - log.Println(err) + slog.Error("Error marshalling entry", "error", err) } return buf.Bytes() diff --git a/internal/web/broadcastmanager.go b/internal/web/broadcastmanager.go index 16f5015..86e761e 100644 --- a/internal/web/broadcastmanager.go +++ b/internal/web/broadcastmanager.go @@ -1,7 +1,7 @@ package web import ( - "log" + "log/slog" "sync" "github.com/d-Rickyy-b/certstream-server-go/internal/metrics" @@ -28,7 +28,7 @@ func NewBroadcastManager() *BroadcastManager { func (bm *BroadcastManager) registerClient(c *client) { bm.clientLock.Lock() bm.clients = append(bm.clients, c) - log.Printf("Clients: %d, Capacity: %d\n", len(bm.clients), cap(bm.clients)) + slog.Info("Client registered", "clients", len(bm.clients), "capacity", cap(bm.clients)) metrics.Prometheus.RegisterClient(c.name, func() float64 { return float64(c.skippedCerts) }) bm.clientLock.Unlock() } @@ -124,7 +124,7 @@ func (bm *BroadcastManager) broadcaster() { case SubTypeDomain: data = dataDomain default: - log.Printf("Unknown subscription type '%d' for client '%s'. Skipping this client!\n", c.subType, c.name) + slog.Warn("Unknown subscription type, skipping client", "sub_type", c.subType, "client", c.name) continue } @@ -134,7 +134,7 @@ func (bm *BroadcastManager) broadcaster() { // Default case is executed if the client's broadcast channel is full. c.skippedCerts++ if c.skippedCerts%1000 == 1 { - log.Printf("Not providing client '%s' with cert because client's buffer is full. The client can't keep up. Skipped certs: %d\n", c.name, c.skippedCerts) + slog.Warn("Client buffer full, skipping certificate", "client", c.name, "skipped_certs", c.skippedCerts) } } } diff --git a/internal/web/client.go b/internal/web/client.go index b11a15c..a2edb5b 100644 --- a/internal/web/client.go +++ b/internal/web/client.go @@ -2,7 +2,7 @@ package web import ( "fmt" - "log" + "log/slog" "strings" "time" @@ -41,7 +41,7 @@ func (c *client) broadcastHandler() { pingTicker := time.NewTicker(30 * time.Second) defer func() { - log.Println("Closing broadcast handler for client:", c.conn.RemoteAddr()) + slog.Info("Closing broadcast handler for client", "remote_addr", c.conn.RemoteAddr()) pingTicker.Stop() @@ -63,17 +63,17 @@ func (c *client) broadcastHandler() { w, err := c.conn.NextWriter(websocket.TextMessage) if err != nil { - log.Printf("Error while getting next writer: %v\n", err) + slog.Error("Error while getting next writer", "error", err) return } _, writeErr := w.Write(message) if writeErr != nil { - log.Printf("Error while writing: %v\n", writeErr) + slog.Error("Error while writing", "error", writeErr) } if closeErr := w.Close(); closeErr != nil { - log.Printf("Error while closing: %v\n", closeErr) + slog.Error("Error while closing writer", "error", closeErr) return } } @@ -122,24 +122,24 @@ func (c *client) listenWebsocket() { _, _, readErr := c.conn.ReadMessage() if readErr != nil { if websocket.IsUnexpectedCloseError(readErr, websocket.CloseGoingAway, websocket.CloseNormalClosure) { - log.Printf("Unexpected websocket close error: %v\n", readErr) + slog.Warn("Unexpected websocket close error", "error", readErr) } // If client fails to send ping messages if strings.Contains(strings.ToLower(readErr.Error()), "i/o timeout") { - log.Printf("No ping received from client: %v\n", c.conn.RemoteAddr()) + slog.Warn("No ping received from client", "remote_addr", c.conn.RemoteAddr()) closeMessage := websocket.FormatCloseMessage(websocket.ClosePolicyViolation, "No ping received!") writeErr := c.conn.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(5*time.Second)) if writeErr != nil { - log.Printf("Error while sending close message: %v\n", writeErr) + slog.Error("Error while sending close message", "error", writeErr) } } else if strings.Contains(strings.ToLower(readErr.Error()), "an existing connection was forcibly closed by the remote host") { - log.Printf("Connection to client lost: %v\n", c.conn.RemoteAddr()) + slog.Info("Connection to client lost", "remote_addr", c.conn.RemoteAddr()) } - log.Printf("Disconnecting client %v!\n", c.conn.RemoteAddr()) + slog.Info("Disconnecting client", "remote_addr", c.conn.RemoteAddr()) break } diff --git a/internal/web/server.go b/internal/web/server.go index c08d78b..41bad05 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -5,7 +5,7 @@ import ( "crypto/tls" "fmt" "io" - "log" + "log/slog" "net" "net/http" "strconv" @@ -15,6 +15,7 @@ import ( "github.com/go-chi/chi/v5/middleware" "github.com/d-Rickyy-b/certstream-server-go/internal/config" + "github.com/d-Rickyy-b/certstream-server-go/internal/logger" "github.com/d-Rickyy-b/certstream-server-go/internal/models" "github.com/gorilla/websocket" @@ -48,7 +49,7 @@ func (ws *Server) RegisterPrometheus(url string, callback func(w io.Writer, expo // IPWhitelist returns a middleware that checks if the IP of the client is in the whitelist. func IPWhitelist(whitelist []string) func(next http.Handler) http.Handler { // build a list of whitelisted IPs and CIDRs - log.Println("Building IP whitelist...") + slog.Info("Building IP whitelist...") var ipList []net.IP var cidrList []net.IPNet @@ -58,7 +59,7 @@ func IPWhitelist(whitelist []string) func(next http.Handler) http.Handler { if err != nil { var ip net.IP if ip = net.ParseIP(element); ip == nil { - log.Println("Invalid IP in metrics whitelist: ", element) + slog.Warn("Invalid IP in metrics whitelist", "ip", element) continue } @@ -71,8 +72,7 @@ func IPWhitelist(whitelist []string) func(next http.Handler) http.Handler { cidrList = append(cidrList, *ipNet) } - log.Println("IP whitelist: ", ipList) - log.Println("CIDR whitelist: ", cidrList) + slog.Info("IP whitelist built", "ips", ipList, "cidrs", cidrList) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -104,7 +104,7 @@ func IPWhitelist(whitelist []string) func(next http.Handler) http.Handler { } } - log.Printf("IP %s not in whitelist, rejecting request\n", r.RemoteAddr) + slog.Warn("IP not in whitelist, rejecting request", "remote_addr", r.RemoteAddr) http.Error(w, "Forbidden", http.StatusForbidden) }) } @@ -115,7 +115,7 @@ func IPWhitelist(whitelist []string) func(next http.Handler) http.Handler { func initFullWebsocket(w http.ResponseWriter, r *http.Request) { connection, err := upgradeConnection(w, r) if err != nil { - log.Println("Error while trying to upgrade connection:", err) + slog.Error("Error while trying to upgrade connection", "error", err) return } @@ -127,7 +127,7 @@ func initFullWebsocket(w http.ResponseWriter, r *http.Request) { func initLiteWebsocket(w http.ResponseWriter, r *http.Request) { connection, err := upgradeConnection(w, r) if err != nil { - log.Println("Error while trying to upgrade connection:", err) + slog.Error("Error while trying to upgrade connection", "error", err) return } @@ -139,7 +139,7 @@ func initLiteWebsocket(w http.ResponseWriter, r *http.Request) { func initDomainWebsocket(w http.ResponseWriter, r *http.Request) { connection, err := upgradeConnection(w, r) if err != nil { - log.Println("Error while trying to upgrade connection:", err) + slog.Error("Error while trying to upgrade connection", "error", err) return } @@ -157,7 +157,7 @@ func upgradeConnection(w http.ResponseWriter, r *http.Request) (*websocket.Conn, remoteAddr = fmt.Sprintf("'%s'", r.RemoteAddr) } - log.Printf("Starting new websocket for %s - %s\n", remoteAddr, r.URL) + slog.Info("Starting new websocket", "remote_addr", remoteAddr, "url", r.URL) connection, err := upgrader.Upgrade(w, r, nil) if err != nil { @@ -166,7 +166,7 @@ func upgradeConnection(w http.ResponseWriter, r *http.Request) (*websocket.Conn, defaultCloseHandler := connection.CloseHandler() connection.SetCloseHandler(func(code int, text string) error { - log.Printf("Stopping websocket for %s - %s\n", remoteAddr, r.URL) + slog.Info("Stopping websocket", "remote_addr", remoteAddr, "url", r.URL) return defaultCloseHandler(code, text) }) @@ -294,7 +294,7 @@ func NewWebsocketServer(networkIf string, port int, certPath, keyPath string) *S // Start initializes the webserver and starts listening for connections. func (ws *Server) Start() { - log.Printf("Starting webserver on %s\n", ws.server.Addr) + slog.Info("Starting webserver", "addr", ws.server.Addr) var err error if ws.keyPath != "" && ws.certPath != "" { @@ -304,16 +304,16 @@ func (ws *Server) Start() { } if err != nil { - log.Fatal("Error while serving webserver: ", err) + logger.Fatal("Error while serving webserver", "error", err) } } // Stop tries to stop the webserver gracefully. If it doesn't stop within 15 seconds, it is forcefully closed. func (ws *Server) Stop() { - log.Println("Stopping webserver...") + slog.Info("Stopping webserver...") if err := ws.shutdown(); err != nil { - log.Fatal("Error while stopping webserver: ", err) + logger.Fatal("Error while stopping webserver", "error", err) } }