diff --git a/cmd/root.go b/cmd/root.go index 4b4013c8c..a89ff11bf 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -579,7 +579,7 @@ func loadConfig(c *Command, args []string, opts []Option) error { // Handle logger separately from config if c.conf.StructuredLogs { - c.logger, c.cleanup = log.NewStructuredLogger(c.conf.Quiet) + c.logger = log.NewStructuredLogger(c.conf.Quiet) } if c.conf.Quiet { diff --git a/go.mod b/go.mod index eb98ac9c4..a635890dc 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/spf13/pflag v1.0.7 github.com/spf13/viper v1.20.1 go.opencensus.io v0.24.0 - go.uber.org/zap v1.27.0 golang.org/x/oauth2 v0.30.0 golang.org/x/sys v0.35.0 google.golang.org/api v0.247.0 @@ -73,7 +72,6 @@ require ( go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect - go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect diff --git a/go.sum b/go.sum index 3e07640cd..b6ddb0077 100644 --- a/go.sum +++ b/go.sum @@ -389,10 +389,6 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/internal/log/log.go b/internal/log/log.go index 6779ee6dd..b88a968ca 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -15,36 +15,35 @@ package log import ( + "fmt" "io" llog "log" + "log/slog" "os" + "time" "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) // StdLogger is the standard logger that distinguishes between info and error // logs. type StdLogger struct { - infoLog *llog.Logger - debugLog *llog.Logger - errLog *llog.Logger + stdLog *llog.Logger + errLog *llog.Logger } // NewStdLogger create a Logger that uses out and err for informational and // error messages. func NewStdLogger(out, err io.Writer) cloudsql.Logger { return &StdLogger{ - infoLog: llog.New(out, "", llog.LstdFlags), - debugLog: llog.New(out, "", llog.LstdFlags), - errLog: llog.New(err, "", llog.LstdFlags), + stdLog: llog.New(out, "", llog.LstdFlags), + errLog: llog.New(err, "", llog.LstdFlags), } } // Infof logs informational messages func (l *StdLogger) Infof(format string, v ...interface{}) { - l.infoLog.Printf(format, v...) + l.stdLog.Printf(format, v...) } // Errorf logs error messages @@ -54,61 +53,72 @@ func (l *StdLogger) Errorf(format string, v ...interface{}) { // Debugf logs debug messages func (l *StdLogger) Debugf(format string, v ...interface{}) { - l.debugLog.Printf(format, v...) + l.stdLog.Printf(format, v...) } // StructuredLogger writes log messages in JSON. type StructuredLogger struct { - logger *zap.SugaredLogger + stdLog *slog.Logger + errLog *slog.Logger } // Infof logs informational messages func (l *StructuredLogger) Infof(format string, v ...interface{}) { - l.logger.Infof(format, v...) + l.stdLog.Info(fmt.Sprintf(format, v...)) } // Errorf logs error messages func (l *StructuredLogger) Errorf(format string, v ...interface{}) { - l.logger.Errorf(format, v...) + l.errLog.Error(fmt.Sprintf(format, v...)) } // Debugf logs debug messages func (l *StructuredLogger) Debugf(format string, v ...interface{}) { - l.logger.Debugf(format, v...) + l.stdLog.Debug(fmt.Sprintf(format, v...)) } // NewStructuredLogger creates a Logger that logs messages using JSON. -func NewStructuredLogger(quiet bool) (cloudsql.Logger, func() error) { - // Configure structured logs to adhere to LogEntry format - // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry - c := zap.NewProductionEncoderConfig() - c.LevelKey = "severity" - c.MessageKey = "message" - c.TimeKey = "timestamp" - c.EncodeLevel = zapcore.CapitalLevelEncoder - c.EncodeTime = zapcore.ISO8601TimeEncoder - - enc := zapcore.NewJSONEncoder(c) - - var syncer zapcore.WriteSyncer - // quiet disables writing to the info log +func NewStructuredLogger(quiet bool) cloudsql.Logger { + var infoHandler, errorHandler slog.Handler if quiet { - syncer = zapcore.AddSync(io.Discard) + infoHandler = slog.DiscardHandler } else { - syncer = zapcore.Lock(os.Stdout) + infoHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + ReplaceAttr: replaceAttr, + }) } - core := zapcore.NewTee( - zapcore.NewCore(enc, syncer, zap.LevelEnablerFunc(func(l zapcore.Level) bool { - // Anything below error, goes to the info log. - return l < zapcore.ErrorLevel - })), - zapcore.NewCore(enc, zapcore.Lock(os.Stderr), zap.LevelEnablerFunc(func(l zapcore.Level) bool { - // Anything at error or higher goes to the error log. - return l >= zapcore.ErrorLevel - })), - ) + errorHandler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + Level: slog.LevelError, + ReplaceAttr: replaceAttr, + }) + l := &StructuredLogger{ - logger: zap.New(core).Sugar(), + stdLog: slog.New(infoHandler), + errLog: slog.New(errorHandler), + } + return l +} + +// replaceAttr remaps default Go logging keys to adhere to LogEntry format +// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry +func replaceAttr(groups []string, a slog.Attr) slog.Attr { + if groups != nil { + return a + } + + switch a.Key { + case slog.LevelKey: + a.Key = "severity" + case slog.MessageKey: + a.Key = "message" + case slog.SourceKey: + a.Key = "sourceLocation" + case slog.TimeKey: + a.Key = "timestamp" + if a.Value.Kind() == slog.KindTime { + a.Value = slog.StringValue(a.Value.Time().Format(time.RFC3339)) + } } - return l, l.logger.Sync + return a }