From 875eaae7cfc18fef9e69154537331da4dd52affd Mon Sep 17 00:00:00 2001 From: slizco Date: Mon, 22 Jul 2024 14:47:58 -0400 Subject: [PATCH] Add v2 svclog unit tests --- cmdutil/v2/svclog/logger.go | 76 +++++--------------------------- cmdutil/v2/svclog/logger_test.go | 71 +++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 66 deletions(-) create mode 100644 cmdutil/v2/svclog/logger_test.go diff --git a/cmdutil/v2/svclog/logger.go b/cmdutil/v2/svclog/logger.go index c48456f2..3ded974d 100644 --- a/cmdutil/v2/svclog/logger.go +++ b/cmdutil/v2/svclog/logger.go @@ -3,6 +3,7 @@ package svclog import ( "fmt" + "io" "log" "os" @@ -16,6 +17,8 @@ type Config struct { SpaceID string `env:"SPACE_ID"` Dyno string `env:"DYNO"` LogLevel string `env:"LOG_LEVEL,default=INFO"` + + WriteTo io.Writer } // NewLogger returns a new logger that includes app and deploy key/value pairs @@ -29,7 +32,12 @@ func NewLogger(cfg Config) *slog.Logger { hopts := &slog.HandlerOptions{ Level: level, } - logger := slog.New(slog.NewTextHandler(os.Stdout, hopts)).With( + var w io.Writer + w = cfg.WriteTo + if w == nil { + w = os.Stdout + } + logger := slog.New(slog.NewTextHandler(w, hopts)).With( "app", cfg.AppName, "deploy", cfg.Deploy, ) @@ -45,7 +53,7 @@ func NewLogger(cfg Config) *slog.Logger { } // ReportPanic attempts to report the panic to rollbar via the slog. -func ReportPanic(logger slog.Logger) { +func ReportPanic(logger *slog.Logger) { if p := recover(); p != nil { s := fmt.Sprint(p) logger.With("at", "panic").Error(s) @@ -53,70 +61,6 @@ func ReportPanic(logger slog.Logger) { } } -/* -// NewSampleLogger creates a rate limited logger that samples logs. The parameter -// logsBurstLimit defines how many logs are allowed per logBurstWindow duration. -// The returned logger derives from the parentLogger, but without inheriting any Hooks. -// All log entries derived from SampleLogger will contain 'sampled=true' field. -func NewSampleLogger(parentLogger slog.Logger, logsBurstLimit int, logBurstWindow time.Duration) slog.Logger { - entry := parentLogger.With("sampled", true) - ll := slog.New() - ll.Out = entry.Logger.Out - ll.Level = entry.Logger.Level - ll.ReportCaller = entry.Logger.ReportCaller - ll.Formatter = &sampleFormatter{ - origFormatter: entry.Logger.Formatter, - limiter: rate.NewLimiter(rate.Every(logBurstWindow), logsBurstLimit), - } - - return ll.With(entry.Data) -} - -type sampleFormatter struct { - limiter *rate.Limiter - origFormatter slog.Formatter -} - -func (sf *sampleFormatter) Format(e *slog.Entry) ([]byte, error) { - if sf.limiter.Allow() { - return sf.origFormatter.Format(e) - } - - return nil, nil -} - -// SaramaLogger takes Logger and returns a saramaLogger. -func SaramaLogger(logger slog.Logger) slog.Logger { - logger = logger.With("component", "sarama") - return saramaLogger{logger} -} - -type saramaLogger struct { - slog.Logger -} - -func (sl saramaLogger) Printf(format string, args ...interface{}) { - format = strings.TrimSpace(format) - sl.Logger.Printf(format, args...) -} - -// NewNullLogger returns a logger that discards the output useful for testing -func NewNullLogger() slog.Logger { - logger := slog.New() - logger.SetOutput(io.Discard) - return logger -} - -// LoggerOrNull ensures non-nil logger is passed in or creates a Null Logger -func LoggerOrNull(l slog.Logger) slog.Logger { - if l == nil { - return NewNullLogger() - } - - return l -} -*/ - func ParseLevel(s string) (slog.Level, error) { var level slog.Level var err = level.UnmarshalText([]byte(s)) diff --git a/cmdutil/v2/svclog/logger_test.go b/cmdutil/v2/svclog/logger_test.go new file mode 100644 index 00000000..5f70dbbb --- /dev/null +++ b/cmdutil/v2/svclog/logger_test.go @@ -0,0 +1,71 @@ +package svclog + +import ( + "bytes" + "fmt" + "strings" + "testing" +) + +func TestLoggerEmitsAppAndDeployData(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + cfg := Config{ + AppName: "sushi", + Deploy: "production", + LogLevel: "INFO", + Dyno: "web.1", + WriteTo: buf, + } + logger := NewLogger(cfg) + logger.Info("message") + + expectLogLine(t, buf, map[string]string{ + "app": "sushi", + "deploy": "production", + "msg": "message", + "dyno": "web.1", + }) +} + +func TestReportPanic(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + cfg := Config{ + AppName: "sushi", + Deploy: "production", + LogLevel: "INFO", + Dyno: "web.1", + WriteTo: buf, + } + logger := NewLogger(cfg) + + defer func() { + if p := recover(); p == nil { + t.Fatal("expected ReportPanic to repanic") + } + + expectLogLine(t, buf, map[string]string{ + "msg": "\"test message\"", + "at": "panic", + "level": "ERROR", + }) + }() + + func() { + defer ReportPanic(logger) + + panic("test message") + }() +} + +func expectLogLine(t *testing.T, buf *bytes.Buffer, m map[string]string) { + msg, err := buf.ReadString('\n') + if err != nil { + t.Fatal(err) + } + + for k, v := range m { + if !strings.Contains(msg, fmt.Sprintf("%s=%s", k, v)) { + t.Errorf("expected log line to contain %s=%s", k, v) + } + } +}