diff --git a/go/vt/logutil/logger.go b/go/vt/logutil/logger.go index 47c3f124238..a9620f536e2 100644 --- a/go/vt/logutil/logger.go +++ b/go/vt/logutil/logger.go @@ -24,7 +24,11 @@ import ( "sync" "time" + noglog "github.com/slok/noglog" + "go.uber.org/zap" + "vitess.io/vitess/go/protoutil" + "vitess.io/vitess/go/vt/log" logutilpb "vitess.io/vitess/go/vt/proto/logutil" ) @@ -383,3 +387,46 @@ func fileAndLine(depth int) (string, int64) { } return file, int64(line) } + +type VTSLogger zap.SugaredLogger + +// SetVTStructureLogger in-place noglog replacement with Zap's logger. +func SetVTStructureLogger(conf *zap.Config) (vtSLogger *zap.SugaredLogger, err error) { + var l *zap.Logger + + // Use the passed configuration instead of the default configuration + if conf == nil { + defaultProdConf := zap.NewProductionConfig() + conf = &defaultProdConf + } + + // Build configuration and generate a sugared logger + l, err = conf.Build() + vtSLogger = l.Sugar() + + noglog.SetLogger(&noglog.LoggerFunc{ + DebugfFunc: func(f string, a ...interface{}) { vtSLogger.Debugf(f, a...) }, + InfofFunc: func(f string, a ...interface{}) { vtSLogger.Infof(f, a...) }, + WarnfFunc: func(f string, a ...interface{}) { vtSLogger.Warnf(f, a...) }, + ErrorfFunc: func(f string, a ...interface{}) { vtSLogger.Errorf(f, a...) }, + }) + + log.Flush = noglog.Flush + log.Info = noglog.Info + log.Infof = noglog.Infof + log.InfoDepth = noglog.InfoDepth + log.Warning = noglog.Warning + log.Warningf = noglog.Warningf + log.WarningDepth = noglog.WarningDepth + log.Error = noglog.Error + log.Errorf = noglog.Errorf + log.ErrorDepth = noglog.ErrorDepth + log.Exit = noglog.Exit + log.Exitf = noglog.Exitf + log.ExitDepth = noglog.ExitDepth + log.Fatal = noglog.Fatal + log.Fatalf = noglog.Fatalf + log.FatalDepth = noglog.FatalDepth + + return +} diff --git a/go/vt/logutil/logger_test.go b/go/vt/logutil/logger_test.go index ce25543da5f..8d7cd61dedb 100644 --- a/go/vt/logutil/logger_test.go +++ b/go/vt/logutil/logger_test.go @@ -17,8 +17,15 @@ limitations under the License. package logutil import ( + "bytes" + "encoding/json" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "net/url" "testing" "time" + vtlog "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/protoutil" "vitess.io/vitess/go/race" @@ -152,3 +159,102 @@ func TestTeeLogger(t *testing.T) { } } } + +// MemorySink implements zap.Sink by writing all messages to a buffer. +// It's used to capture the logs. +type MemorySink struct { + *bytes.Buffer +} + +// Implement Close and Sync as no-ops to satisfy the interface. The Write +// method is provided by the embedded buffer. +func (s *MemorySink) Close() error { return nil } +func (s *MemorySink) Sync() error { return nil } + +func SetupLoggerWithMemSink() (sink *MemorySink, err error) { + // Create a sink instance, and register it with zap for the "memory" protocol. + sink = &MemorySink{new(bytes.Buffer)} + err = zap.RegisterSink("memory", func(*url.URL) (zap.Sink, error) { + return sink, nil + }) + if err != nil { + return nil, err + } + + testLoggerConf := NewMemorySinkConfig() + _, err = SetVTStructureLogger(&testLoggerConf) + if err != nil { + return nil, err + } + + return +} + +func NewMemorySinkConfig() zap.Config { + return zap.Config{ + Level: zap.NewAtomicLevelAt(zap.InfoLevel), + Development: false, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: zap.NewProductionEncoderConfig(), + OutputPaths: []string{"memory://"}, + ErrorOutputPaths: []string{"memory://"}, + } +} + +func TestVTSLogger_Replacing_glog(t *testing.T) { + type logMsg struct { + Level string `json:"level"` + Msg string `json:"msg"` + } + + type testCase struct { + name string + logLevel zapcore.Level + } + + dummyLogMessage := "testing log" + testCases := []testCase{ + {"log info", zap.InfoLevel}, + {"log warn", zap.WarnLevel}, + {"log error", zap.ErrorLevel}, + } + + sink, err := SetupLoggerWithMemSink() + assert.NoError(t, err) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var loggingFunc func(format string, args ...interface{}) + var expectedLevel string + + switch tc.logLevel { + case zapcore.InfoLevel: + loggingFunc = vtlog.Infof + expectedLevel = "info" + case zapcore.ErrorLevel: + loggingFunc = vtlog.Errorf + expectedLevel = "error" + case zapcore.WarnLevel: + loggingFunc = vtlog.Warningf + expectedLevel = "warn" + } + + loggingFunc(dummyLogMessage) + + // Unmarshal the captured log. This means we're getting a struct log. + actualLog := logMsg{} + err = json.Unmarshal(sink.Bytes(), &actualLog) + assert.NoError(t, err) + // Reset the sink so that it'll contain one log per test case. + sink.Reset() + + assert.Equal(t, expectedLevel, actualLog.Level) + assert.Equal(t, dummyLogMessage, actualLog.Msg) + + }) + } +} diff --git a/go/vt/logutil/vts_logger.go b/go/vt/logutil/vts_logger.go deleted file mode 100644 index 83cbe795181..00000000000 --- a/go/vt/logutil/vts_logger.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2023 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package logutil - -import ( - noglog "github.com/slok/noglog" - "go.uber.org/zap" - - "vitess.io/vitess/go/vt/log" -) - -type VTSLogger zap.SugaredLogger - -// SetVTStructureLogger in-place noglog replacement with Zap's logger. -func SetVTStructureLogger(conf *zap.Config) (vtSLogger *zap.SugaredLogger, err error) { - var l *zap.Logger - - // Use the passed configuration instead of the default configuration - if conf == nil { - defaultProdConf := zap.NewProductionConfig() - conf = &defaultProdConf - } - - // Build configuration and generate a sugared logger - l, err = conf.Build() - vtSLogger = l.Sugar() - - noglog.SetLogger(&noglog.LoggerFunc{ - DebugfFunc: func(f string, a ...interface{}) { vtSLogger.Debugf(f, a...) }, - InfofFunc: func(f string, a ...interface{}) { vtSLogger.Infof(f, a...) }, - WarnfFunc: func(f string, a ...interface{}) { vtSLogger.Warnf(f, a...) }, - ErrorfFunc: func(f string, a ...interface{}) { vtSLogger.Errorf(f, a...) }, - }) - - log.Flush = noglog.Flush - log.Info = noglog.Info - log.Infof = noglog.Infof - log.InfoDepth = noglog.InfoDepth - log.Warning = noglog.Warning - log.Warningf = noglog.Warningf - log.WarningDepth = noglog.WarningDepth - log.Error = noglog.Error - log.Errorf = noglog.Errorf - log.ErrorDepth = noglog.ErrorDepth - log.Exit = noglog.Exit - log.Exitf = noglog.Exitf - log.ExitDepth = noglog.ExitDepth - log.Fatal = noglog.Fatal - log.Fatalf = noglog.Fatalf - log.FatalDepth = noglog.FatalDepth - - return -} diff --git a/go/vt/logutil/vts_logger_test.go b/go/vt/logutil/vts_logger_test.go deleted file mode 100644 index 9240dd7323a..00000000000 --- a/go/vt/logutil/vts_logger_test.go +++ /dev/null @@ -1,130 +0,0 @@ -/* -Copyright 2023 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package logutil - -import ( - "bytes" - "encoding/json" - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - vtlog "vitess.io/vitess/go/vt/log" -) - -// MemorySink implements zap.Sink by writing all messages to a buffer. -// It's used to capture the logs. -type MemorySink struct { - *bytes.Buffer -} - -// Implement Close and Sync as no-ops to satisfy the interface. The Write -// method is provided by the embedded buffer. -func (s *MemorySink) Close() error { return nil } -func (s *MemorySink) Sync() error { return nil } - -func SetupLoggerWithMemSink() (sink *MemorySink, err error) { - // Create a sink instance, and register it with zap for the "memory" protocol. - sink = &MemorySink{new(bytes.Buffer)} - err = zap.RegisterSink("memory", func(*url.URL) (zap.Sink, error) { - return sink, nil - }) - if err != nil { - return nil, err - } - - testLoggerConf := NewMemorySinkConfig() - _, err = SetVTStructureLogger(&testLoggerConf) - if err != nil { - return nil, err - } - - return -} - -func NewMemorySinkConfig() zap.Config { - return zap.Config{ - Level: zap.NewAtomicLevelAt(zap.InfoLevel), - Development: false, - Sampling: &zap.SamplingConfig{ - Initial: 100, - Thereafter: 100, - }, - Encoding: "json", - EncoderConfig: zap.NewProductionEncoderConfig(), - OutputPaths: []string{"memory://"}, - ErrorOutputPaths: []string{"memory://"}, - } -} - -func TestVTSLogger_Replacing_glog(t *testing.T) { - type logMsg struct { - Level string `json:"level"` - Msg string `json:"msg"` - } - - type testCase struct { - name string - logLevel zapcore.Level - } - - dummyLogMessage := "testing log" - testCases := []testCase{ - {"log info", zap.InfoLevel}, - {"log warn", zap.WarnLevel}, - {"log error", zap.ErrorLevel}, - } - - sink, err := SetupLoggerWithMemSink() - assert.NoError(t, err) - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var loggingFunc func(format string, args ...interface{}) - var expectedLevel string - - switch tc.logLevel { - case zapcore.InfoLevel: - loggingFunc = vtlog.Infof - expectedLevel = "info" - case zapcore.ErrorLevel: - loggingFunc = vtlog.Errorf - expectedLevel = "error" - case zapcore.WarnLevel: - loggingFunc = vtlog.Warningf - expectedLevel = "warn" - } - - loggingFunc(dummyLogMessage) - - // Unmarshal the captured log. This means we're getting a struct log. - actualLog := logMsg{} - err = json.Unmarshal(sink.Bytes(), &actualLog) - assert.NoError(t, err) - // Reset the sink so that it'll contain one log per test case. - sink.Reset() - - assert.Equal(t, expectedLevel, actualLog.Level) - assert.Equal(t, dummyLogMessage, actualLog.Msg) - - }) - } -}