From a98e802f559cade1290f1d40855f24bfe27f1b15 Mon Sep 17 00:00:00 2001 From: Christian Schaible Date: Mon, 7 Oct 2024 13:57:06 +0200 Subject: [PATCH] Add log abstraction --- audit/api/api_legacy_dynamic_test.go | 3 - audit/api/api_legacy_test.go | 3 - audit/api/builder.go | 4 +- audit/api/log.go | 6 +- audit/messaging/messaging.go | 12 +-- audit/messaging/solace.go | 4 +- go.mod | 3 + go.sum | 13 +++ log/log.go | 82 +++++++++++++++++++ log/log_test.go | 113 +++++++++++++++++++++++++++ 10 files changed, 224 insertions(+), 19 deletions(-) create mode 100644 log/log.go create mode 100644 log/log_test.go diff --git a/audit/api/api_legacy_dynamic_test.go b/audit/api/api_legacy_dynamic_test.go index 87f7ff8..38934fa 100644 --- a/audit/api/api_legacy_dynamic_test.go +++ b/audit/api/api_legacy_dynamic_test.go @@ -4,9 +4,7 @@ import ( "context" "encoding/json" "errors" - "log/slog" "net/url" - "os" "strings" "testing" "time" @@ -20,7 +18,6 @@ import ( ) func TestDynamicLegacyAuditApi(t *testing.T) { - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) // Specify test timeout ctx, cancelFn := context.WithTimeout(context.Background(), 120*time.Second) diff --git a/audit/api/api_legacy_test.go b/audit/api/api_legacy_test.go index 6dd37e7..bb48efb 100644 --- a/audit/api/api_legacy_test.go +++ b/audit/api/api_legacy_test.go @@ -5,9 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "log/slog" "net/url" - "os" "strings" "testing" "time" @@ -22,7 +20,6 @@ import ( ) func TestLegacyAuditApi(t *testing.T) { - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) // Specify test timeout ctx, cancelFn := context.WithTimeout(context.Background(), 120*time.Second) diff --git a/audit/api/builder.go b/audit/api/builder.go index 1709ff2..2c41b73 100644 --- a/audit/api/builder.go +++ b/audit/api/builder.go @@ -4,10 +4,10 @@ import ( "context" "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/audit/utils" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/gen/go/audit/v1" + "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/log" "errors" "fmt" "go.opentelemetry.io/otel/trace" - "log/slog" "time" ) @@ -59,7 +59,7 @@ func getObjectIdAndTypeFromAuditParams( // Convert to plural type plural, err := objectType.AsPluralType() if err != nil { - slog.LogAttrs(ctx, slog.LevelError, "failed to convert singular type to plural type", slog.Any("error", err)) + log.AuditLogger.Error("failed to convert singular type to plural type", err) return "", nil, nil, err } return objectId, objectType, &plural, nil diff --git a/audit/api/log.go b/audit/api/log.go index d25fc83..7ecccf0 100644 --- a/audit/api/log.go +++ b/audit/api/log.go @@ -2,11 +2,11 @@ package api import ( auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/gen/go/audit/v1" + "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/log" "encoding/json" "errors" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" - "log/slog" "time" ) @@ -14,7 +14,7 @@ import ( func LogEvent(event *CloudEvent) error { if event.DataType == DataTypeLegacyAuditEventV1 { - slog.Info(string(event.Data)) + log.AuditLogger.Info(string(event.Data)) return nil } else if event.DataType != "audit.v1.RoutableAuditEvent" { return errors.New("Unsupported data type " + event.DataType) @@ -75,7 +75,7 @@ func LogEvent(event *CloudEvent) error { return err } - slog.Info(string(cloudEventJson)) + log.AuditLogger.Info(string(cloudEventJson)) return nil } diff --git a/audit/messaging/messaging.go b/audit/messaging/messaging.go index cfae248..6a3d57d 100644 --- a/audit/messaging/messaging.go +++ b/audit/messaging/messaging.go @@ -2,10 +2,10 @@ package messaging import ( "context" + "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/log" "errors" "fmt" "github.com/Azure/go-amqp" - "log/slog" "strings" "sync" "time" @@ -111,16 +111,16 @@ func NewAmqpApi(amqpConfig AmqpConfig) (*Api, error) { // connect opens a new connection and session to the AMQP messaging system. // The connection attempt will be cancelled after connectionTimeoutSeconds. func (a *AmqpApi) connect() error { - slog.Info("connecting to messaging system") + log.AuditLogger.Info("connecting to messaging system") // Set credentials if specified auth := amqp.SASLTypeAnonymous() if a.config.User != "" && a.config.Password != "" { auth = amqp.SASLTypePlain(a.config.User, a.config.Password) - slog.Info("using username and password for messaging") + log.AuditLogger.Info("using username and password for messaging") } else { - slog.Warn("using anonymous messaging!") + log.AuditLogger.Warn("using anonymous messaging!") } options := &amqp.ConnOptions{ @@ -159,7 +159,7 @@ func (a *AmqpApi) Send(ctx context.Context, topic string, data []byte, contentTy } // Drop the current sender, as it cannot connect to the broker anymore - slog.Error("message sender error, recreating", slog.Any("error", err)) + log.AuditLogger.Error("message sender error, recreating", err) err = a.resetConnection(ctx) if err != nil { @@ -211,7 +211,7 @@ func (a *AmqpApi) resetConnection(ctx context.Context) error { _ = (*a.session).Close(ctx) err := a.connection.Close() if err != nil { - slog.Error("failed to close message connection", slog.Any("error", err)) + log.AuditLogger.Error("failed to close message connection", err) } return a.connect() diff --git a/audit/messaging/solace.go b/audit/messaging/solace.go index 237a500..a4ae435 100644 --- a/audit/messaging/solace.go +++ b/audit/messaging/solace.go @@ -3,6 +3,7 @@ package messaging import ( "bytes" "context" + "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/log" "encoding/json" "errors" "fmt" @@ -10,7 +11,6 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" "io" - "log/slog" "net/http" "regexp" "strings" @@ -168,7 +168,7 @@ func NewSolaceContainer(ctx context.Context) (*SolaceContainer, error) { _ = container.Terminate(ctx) return nil, err } - slog.Info("UI Port: " + sempPort.Port()) + log.AuditLogger.Info("UI Port: " + sempPort.Port()) // Construct connection strings amqpConnectionString := fmt.Sprintf("amqp://%s:%s/", host, amqpPort.Port()) diff --git a/go.mod b/go.mod index bf2310c..4379c19 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Azure/go-amqp v1.1.0 github.com/bufbuild/protovalidate-go v0.6.4 github.com/google/uuid v1.6.0 + github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 go.opentelemetry.io/otel v1.24.0 @@ -38,6 +39,8 @@ require ( github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect diff --git a/go.sum b/go.sum index 381efa0..ebe381f 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -50,6 +51,7 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= @@ -74,6 +76,11 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= @@ -98,6 +105,9 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -171,8 +181,11 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..b1a8266 --- /dev/null +++ b/log/log.go @@ -0,0 +1,82 @@ +package log + +import ( + "errors" + "log/slog" +) +import "github.com/rs/zerolog/log" + +var AuditLogger = NewSlogAuditLogger(slog.Default()) + +func NewSlogAuditLogger(logger *slog.Logger) Logger { + return SlogLogger{logger: logger} +} + +func NewZerologAuditLogger() Logger { + return ZeroLogLogger{} +} + +type Logger interface { + Debug(msg string, err ...error) + Info(msg string, err ...error) + Warn(msg string, err ...error) + Error(msg string, err ...error) +} + +type SlogLogger struct { + logger *slog.Logger +} + +func (s SlogLogger) Debug(msg string, err ...error) { + s.logger.Debug(msg, s.getWrappedError(err)) +} + +func (s SlogLogger) Info(msg string, err ...error) { + s.logger.Info(msg, s.getWrappedError(err)) +} + +func (s SlogLogger) Warn(msg string, err ...error) { + s.logger.Warn(msg, s.getWrappedError(err)) +} + +func (s SlogLogger) Error(msg string, err ...error) { + s.logger.Error(msg, s.getWrappedError(err)) +} + +func (s SlogLogger) getWrappedError(err []error) slog.Attr { + var wrappedErr slog.Attr + if err != nil { + wrappedErr = slog.Any("error", wrapErr(err)) + } + return wrappedErr +} + +type ZeroLogLogger struct{} + +func (l ZeroLogLogger) Debug(msg string, err ...error) { + log.Debug().Err(wrapErr(err)).Msg(msg) +} + +func (l ZeroLogLogger) Info(msg string, err ...error) { + log.Info().Err(wrapErr(err)).Msg(msg) +} + +func (l ZeroLogLogger) Warn(msg string, err ...error) { + log.Warn().Err(wrapErr(err)).Msg(msg) +} + +func (l ZeroLogLogger) Error(msg string, err ...error) { + log.Error().Err(wrapErr(err)).Msg(msg) +} + +func wrapErr(err []error) error { + var e error + if len(err) == 0 { + e = nil + } else if len(err) == 1 { + e = err[0] + } else { + e = errors.Join(err...) + } + return e +} diff --git a/log/log_test.go b/log/log_test.go new file mode 100644 index 0000000..1e1ab7f --- /dev/null +++ b/log/log_test.go @@ -0,0 +1,113 @@ +package log + +import ( + "errors" + "log/slog" + "testing" +) + +func Test_DefaultLogger(t *testing.T) { + t.Run("debug", func(t *testing.T) { + AuditLogger.Debug("debug message") + }) + + t.Run("debug with error details", func(t *testing.T) { + AuditLogger.Debug("debug message", errors.New("custom error")) + }) + + t.Run("info", func(t *testing.T) { + AuditLogger.Info("info message") + }) + + t.Run("info with error details", func(t *testing.T) { + AuditLogger.Info("info message", errors.New("custom error")) + }) + + t.Run("warn", func(t *testing.T) { + AuditLogger.Warn("warn message") + }) + + t.Run("warn with error details", func(t *testing.T) { + AuditLogger.Warn("warn message", errors.New("custom error")) + }) + + t.Run("error", func(t *testing.T) { + AuditLogger.Error("error message") + }) + + t.Run("error with error details", func(t *testing.T) { + AuditLogger.Error("error message", errors.New("custom error")) + }) +} + +func Test_SlogLogger(t *testing.T) { + AuditLogger = NewSlogAuditLogger(slog.Default()) + + t.Run("debug", func(t *testing.T) { + AuditLogger.Debug("debug message") + }) + + t.Run("debug with error details", func(t *testing.T) { + AuditLogger.Debug("debug message", errors.New("custom error")) + }) + + t.Run("info", func(t *testing.T) { + AuditLogger.Info("info message") + }) + + t.Run("info with error details", func(t *testing.T) { + AuditLogger.Info("info message", errors.New("custom error")) + }) + + t.Run("warn", func(t *testing.T) { + AuditLogger.Warn("warn message") + }) + + t.Run("warn with error details", func(t *testing.T) { + AuditLogger.Warn("warn message", errors.New("custom error")) + }) + + t.Run("error", func(t *testing.T) { + AuditLogger.Error("error message") + }) + + t.Run("error with error details", func(t *testing.T) { + AuditLogger.Error("error message", errors.New("custom error")) + }) +} + +func Test_ZerologLogger(t *testing.T) { + AuditLogger = NewZerologAuditLogger() + + t.Run("debug", func(t *testing.T) { + AuditLogger.Debug("debug message") + }) + + t.Run("debug with error details", func(t *testing.T) { + AuditLogger.Debug("debug message", errors.New("custom error")) + }) + + t.Run("info", func(t *testing.T) { + AuditLogger.Info("info message") + }) + + t.Run("info with error details", func(t *testing.T) { + AuditLogger.Info("info message", errors.New("custom error")) + }) + + t.Run("warn", func(t *testing.T) { + AuditLogger.Warn("warn message") + }) + + t.Run("warn with error details", func(t *testing.T) { + AuditLogger.Warn("warn message", errors.New("custom error")) + }) + + t.Run("error", func(t *testing.T) { + AuditLogger.Error("error message") + }) + + t.Run("error with error details", func(t *testing.T) { + AuditLogger.Error("error message", errors.New("custom error")) + }) +}