From 3bd9037673f78505b8b8b4ed03e2c863fb873ac4 Mon Sep 17 00:00:00 2001 From: Ales Verbic Date: Tue, 31 Dec 2024 11:17:12 -0500 Subject: [PATCH] feat: switch logging to slog Signed-off-by: Ales Verbic --- cmd/cardano-node-api/main.go | 32 ++++------ internal/api/api.go | 33 +++++----- internal/api/localtxsubmission.go | 8 +-- internal/logging/logging.go | 101 ++++++++++++++++++------------ internal/utxorpc/api.go | 10 +-- 5 files changed, 98 insertions(+), 86 deletions(-) diff --git a/cmd/cardano-node-api/main.go b/cmd/cardano-node-api/main.go index 3610488..c54cc5c 100644 --- a/cmd/cardano-node-api/main.go +++ b/cmd/cardano-node-api/main.go @@ -52,40 +52,29 @@ func main() { } // Configure logging - logging.Setup(&cfg.Logging) + logging.Configure() logger := logging.GetLogger() - // Sync logger on exit - defer func() { - if err := logger.Sync(); err != nil { - // We don't actually care about the error here, but we have to do something - // to appease the linter - return - } - }() // Test node connection if cfg.Node.SkipCheck { - logger.Debugf("skipping node check") + logger.Debug("skipping node check") } else { if oConn, err := node.GetConnection(nil); err != nil { - logger.Fatalf("failed to connect to node: %s", err) + logger.Error("failed to connect to node:", "error", err) } else { oConn.Close() } } - logger.Infof( - "starting cardano-node-api version %s", - version.GetVersionString(), - ) + logger.Info("starting cardano-node-api version", "version", version.GetVersionString()) // Start debug listener if cfg.Debug.ListenPort > 0 { - logger.Infof( + logger.Info(fmt.Sprintf( "starting debug listener on %s:%d", cfg.Debug.ListenAddress, cfg.Debug.ListenPort, - ) + )) go func() { err := http.ListenAndServe( fmt.Sprintf( @@ -96,7 +85,8 @@ func main() { nil, ) if err != nil { - logger.Fatalf("failed to start debug listener: %s", err) + logger.Error("failed to start debug listener:", "error", err) + os.Exit(1) } }() } @@ -104,13 +94,15 @@ func main() { // Start API listener go func() { if err := api.Start(cfg); err != nil { - logger.Fatalf("failed to start API: %s", err) + logger.Error("failed to start API:", "error", err) + os.Exit(1) } }() // Start UTxO RPC gRPC listener if err := utxorpc.Start(cfg); err != nil { - logger.Fatalf("failed to start gRPC: %s", err) + logger.Error("failed to start gRPC:", "error", err) + os.Exit(1) } // Wait forever diff --git a/internal/api/api.go b/internal/api/api.go index 64565c5..0c9f1a1 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -16,12 +16,11 @@ package api import ( "fmt" - "time" + "os" "github.com/blinklabs-io/cardano-node-api/internal/config" "github.com/blinklabs-io/cardano-node-api/internal/logging" - ginzap "github.com/gin-contrib/zap" "github.com/gin-gonic/gin" "github.com/penglongli/gin-metrics/ginmetrics" @@ -44,17 +43,17 @@ func Start(cfg *config.Config) error { // Standard logging logger := logging.GetLogger() if cfg.Tls.CertFilePath != "" && cfg.Tls.KeyFilePath != "" { - logger.Infof( + logger.Info(fmt.Sprintf( "starting API TLS listener on %s:%d", cfg.Api.ListenAddress, cfg.Api.ListenPort, - ) + )) } else { - logger.Infof( + logger.Info(fmt.Sprintf( "starting API listener on %s:%d", cfg.Api.ListenAddress, cfg.Api.ListenPort, - ) + )) } // Disable gin debug and color output gin.SetMode(gin.ReleaseMode) @@ -69,14 +68,15 @@ func Start(cfg *config.Config) error { skipPaths := []string{} if cfg.Logging.Healthchecks { skipPaths = append(skipPaths, "/healthcheck") - logger.Infof("disabling access logs for /healthcheck") + logger.Info("disabling access logs for /healthcheck") } - router.Use(ginzap.GinzapWithConfig(accessLogger, &ginzap.Config{ - TimeFormat: time.RFC3339, - UTC: true, - SkipPaths: skipPaths, - })) - router.Use(ginzap.RecoveryWithZap(accessLogger, true)) + accessMiddleware := func(c *gin.Context) { + accessLogger.Info("request received", "method", c.Request.Method, "path", c.Request.URL.Path, "remote_addr", c.ClientIP()) + c.Next() + statusCode := c.Writer.Status() + accessLogger.Info("response sent", "status", statusCode, "method", c.Request.Method, "path", c.Request.URL.Path, "remote_addr", c.ClientIP()) + } + router.Use(accessMiddleware) // Create a healthcheck router.GET("/healthcheck", handleHealthcheck) @@ -104,14 +104,15 @@ func Start(cfg *config.Config) error { // Start metrics listener go func() { // TODO: return error if we cannot initialize metrics - logger.Infof("starting metrics listener on %s:%d", + logger.Info(fmt.Sprintf("starting metrics listener on %s:%d", cfg.Metrics.ListenAddress, - cfg.Metrics.ListenPort) + cfg.Metrics.ListenPort)) err := metricsRouter.Run(fmt.Sprintf("%s:%d", cfg.Metrics.ListenAddress, cfg.Metrics.ListenPort)) if err != nil { - logger.Fatalf("failed to start metrics listener: %s", err) + logger.Error("failed to start metrics listener:", "error", err) + os.Exit(1) } }() diff --git a/internal/api/localtxsubmission.go b/internal/api/localtxsubmission.go index 36bfb2b..7c95c65 100644 --- a/internal/api/localtxsubmission.go +++ b/internal/api/localtxsubmission.go @@ -50,7 +50,7 @@ func handleLocalSubmitTx(c *gin.Context) { // Check our headers for content-type if c.ContentType() != "application/cbor" { // Log the error, return an error to the user, and increment failed count - logger.Errorf("invalid request body, should be application/cbor") + logger.Error("invalid request body, should be application/cbor") c.JSON(415, "invalid request body, should be application/cbor") // _ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil) return @@ -59,7 +59,7 @@ func handleLocalSubmitTx(c *gin.Context) { txRawBytes, err := io.ReadAll(c.Request.Body) if err != nil { // Log the error, return an error to the user, and increment failed count - logger.Errorf("failed to read request body: %s", err) + logger.Error("failed to read request body:", "error", err) c.JSON(500, "failed to read request body") // _ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil) return @@ -67,7 +67,7 @@ func handleLocalSubmitTx(c *gin.Context) { // Close request body after read if c.Request.Body != nil { if err = c.Request.Body.Close(); err != nil { - logger.Errorf("failed to close request body: %s", err) + logger.Error("failed to close request body:", "error", err) } } // Send TX @@ -99,7 +99,7 @@ func handleLocalSubmitTx(c *gin.Context) { go func() { err, ok := <-errorChan if ok { - logger.Errorf("failure communicating with node: %s", err) + logger.Error("failure communicating with node:", "error", err) c.JSON(500, "failure communicating with node") // _ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil) } diff --git a/internal/logging/logging.go b/internal/logging/logging.go index 56c4498..66ea432 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -1,4 +1,4 @@ -// Copyright 2023 Blink Labs Software +// Copyright 2024 Blink Labs Software // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,56 +15,75 @@ package logging import ( - "github.com/blinklabs-io/cardano-node-api/internal/config" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "log" + "log/slog" + "os" "time" -) - -type Logger = zap.SugaredLogger -var globalLogger *Logger + "github.com/blinklabs-io/cardano-node-api/internal/config" +) -func Setup(cfg *config.LoggingConfig) { - // Build our custom logging config - loggerConfig := zap.NewProductionConfig() - // Change timestamp key name - loggerConfig.EncoderConfig.TimeKey = "timestamp" - // Use a human readable time format - loggerConfig.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout( - time.RFC3339, - ) +var globalLogger *slog.Logger +var accessLogger *slog.Logger - // Set level - if cfg.Level != "" { - level, err := zapcore.ParseLevel(cfg.Level) - if err != nil { - log.Fatalf("error configuring logger: %s", err) - } - loggerConfig.Level.SetLevel(level) +// Configure initializes the global logger. +func Configure() { + cfg := config.GetConfig() + var level slog.Level + switch cfg.Logging.Level { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + level = slog.LevelInfo } - // Create the logger - l, err := loggerConfig.Build() - if err != nil { - log.Fatal(err) - } + handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + return slog.String( + "timestamp", + a.Value.Time().Format(time.RFC3339), + ) + } + return a + }, + Level: level, + }) + globalLogger = slog.New(handler).With("component", "main") - // Store the "sugared" version of the logger - globalLogger = l.Sugar() + // Configure access logger + accessHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + return slog.String( + "timestamp", + a.Value.Time().Format(time.RFC3339), + ) + } + return a + }, + Level: slog.LevelInfo, + }) + accessLogger = slog.New(accessHandler).With("component", "access") } -func GetLogger() *zap.SugaredLogger { +// GetLogger returns the global application logger. +func GetLogger() *slog.Logger { + if globalLogger == nil { + Configure() + } return globalLogger } -func GetDesugaredLogger() *zap.Logger { - return globalLogger.Desugar() -} - -func GetAccessLogger() *zap.Logger { - return globalLogger.Desugar(). - With(zap.String("type", "access")). - WithOptions(zap.WithCaller(false)) +// GetAccessLogger returns the access logger. +func GetAccessLogger() *slog.Logger { + if accessLogger == nil { + Configure() + } + return accessLogger } diff --git a/internal/utxorpc/api.go b/internal/utxorpc/api.go index a994b1b..99ebb08 100644 --- a/internal/utxorpc/api.go +++ b/internal/utxorpc/api.go @@ -36,17 +36,17 @@ func Start(cfg *config.Config) error { // Standard logging logger := logging.GetLogger() if cfg.Tls.CertFilePath != "" && cfg.Tls.KeyFilePath != "" { - logger.Infof( - "starting gRPC TLS listener on %s:%d", + logger.Info(fmt.Sprintf( + "starting gRPC TLS listener on: %s:%d", cfg.Utxorpc.ListenAddress, cfg.Utxorpc.ListenPort, - ) + )) } else { - logger.Infof( + logger.Info(fmt.Sprintf( "starting gRPC listener on %s:%d", cfg.Utxorpc.ListenAddress, cfg.Utxorpc.ListenPort, - ) + )) } mux := http.NewServeMux() compress1KB := connect.WithCompressMinBytes(1024)