Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
94 changes: 52 additions & 42 deletions internal/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Loading