diff --git a/pkg/logutil/logger.go b/pkg/logutil/logger.go index ecc9ae5b3..e7da80eff 100644 --- a/pkg/logutil/logger.go +++ b/pkg/logutil/logger.go @@ -17,6 +17,7 @@ package logutil import "google.golang.org/grpc/grpclog" // Logger defines logging interface. +// TODO: deprecate in v3.5. type Logger interface { grpclog.LoggerV2 diff --git a/pkg/logutil/zap.go b/pkg/logutil/zap.go new file mode 100644 index 000000000..440173911 --- /dev/null +++ b/pkg/logutil/zap.go @@ -0,0 +1,166 @@ +// Copyright 2018 The etcd 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 ( + "github.com/coreos/etcd/raft" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "google.golang.org/grpc/grpclog" +) + +// NewGRPCLoggerV2 converts "*zap.Logger" to "grpclog.LoggerV2". +// It discards all INFO level logging in gRPC, if debug level +// is not enabled in "*zap.Logger". +func NewGRPCLoggerV2(lcfg zap.Config) (grpclog.LoggerV2, error) { + lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil" + if err != nil { + return nil, err + } + return &zapGRPCLogger{lg: lg, sugar: lg.Sugar()}, nil +} + +type zapGRPCLogger struct { + lg *zap.Logger + sugar *zap.SugaredLogger +} + +func (zl *zapGRPCLogger) Info(args ...interface{}) { + if !zl.lg.Core().Enabled(zapcore.DebugLevel) { + return + } + zl.sugar.Info(args...) +} + +func (zl *zapGRPCLogger) Infoln(args ...interface{}) { + if !zl.lg.Core().Enabled(zapcore.DebugLevel) { + return + } + zl.sugar.Info(args...) +} + +func (zl *zapGRPCLogger) Infof(format string, args ...interface{}) { + if !zl.lg.Core().Enabled(zapcore.DebugLevel) { + return + } + zl.sugar.Infof(format, args...) +} + +func (zl *zapGRPCLogger) Warning(args ...interface{}) { + zl.sugar.Warn(args...) +} + +func (zl *zapGRPCLogger) Warningln(args ...interface{}) { + zl.sugar.Warn(args...) +} + +func (zl *zapGRPCLogger) Warningf(format string, args ...interface{}) { + zl.sugar.Warnf(format, args...) +} + +func (zl *zapGRPCLogger) Error(args ...interface{}) { + zl.sugar.Error(args...) +} + +func (zl *zapGRPCLogger) Errorln(args ...interface{}) { + zl.sugar.Error(args...) +} + +func (zl *zapGRPCLogger) Errorf(format string, args ...interface{}) { + zl.sugar.Errorf(format, args...) +} + +func (zl *zapGRPCLogger) Fatal(args ...interface{}) { + zl.sugar.Fatal(args...) +} + +func (zl *zapGRPCLogger) Fatalln(args ...interface{}) { + zl.sugar.Fatal(args...) +} + +func (zl *zapGRPCLogger) Fatalf(format string, args ...interface{}) { + zl.sugar.Fatalf(format, args...) +} + +func (zl *zapGRPCLogger) V(l int) bool { + // infoLog == 0 + if l <= 0 { // debug level, then we ignore info level in gRPC + return !zl.lg.Core().Enabled(zapcore.DebugLevel) + } + return true +} + +// NewRaftLogger converts "*zap.Logger" to "raft.Logger". +func NewRaftLogger(lcfg zap.Config) (raft.Logger, error) { + lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil" + if err != nil { + return nil, err + } + return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}, nil +} + +type zapRaftLogger struct { + lg *zap.Logger + sugar *zap.SugaredLogger +} + +func (zl *zapRaftLogger) Debug(args ...interface{}) { + zl.sugar.Debug(args...) +} + +func (zl *zapRaftLogger) Debugf(format string, args ...interface{}) { + zl.sugar.Debugf(format, args...) +} + +func (zl *zapRaftLogger) Error(args ...interface{}) { + zl.sugar.Error(args...) +} + +func (zl *zapRaftLogger) Errorf(format string, args ...interface{}) { + zl.sugar.Errorf(format, args...) +} + +func (zl *zapRaftLogger) Info(args ...interface{}) { + zl.sugar.Info(args...) +} + +func (zl *zapRaftLogger) Infof(format string, args ...interface{}) { + zl.sugar.Infof(format, args...) +} + +func (zl *zapRaftLogger) Warning(args ...interface{}) { + zl.sugar.Warn(args...) +} + +func (zl *zapRaftLogger) Warningf(format string, args ...interface{}) { + zl.sugar.Warnf(format, args...) +} + +func (zl *zapRaftLogger) Fatal(args ...interface{}) { + zl.sugar.Fatal(args...) +} + +func (zl *zapRaftLogger) Fatalf(format string, args ...interface{}) { + zl.sugar.Fatalf(format, args...) +} + +func (zl *zapRaftLogger) Panic(args ...interface{}) { + zl.sugar.Panic(args...) +} + +func (zl *zapRaftLogger) Panicf(format string, args ...interface{}) { + zl.sugar.Panicf(format, args...) +} diff --git a/pkg/logutil/zap_test.go b/pkg/logutil/zap_test.go new file mode 100644 index 000000000..15ac2c006 --- /dev/null +++ b/pkg/logutil/zap_test.go @@ -0,0 +1,115 @@ +// Copyright 2018 The etcd 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" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "go.uber.org/zap" +) + +func TestNewGRPCLoggerV2(t *testing.T) { + logPath := filepath.Join(os.TempDir(), fmt.Sprintf("test-log-%d", time.Now().UnixNano())) + defer os.RemoveAll(logPath) + + lcfg := zap.Config{ + Level: zap.NewAtomicLevelAt(zap.InfoLevel), + Development: false, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: zap.NewProductionEncoderConfig(), + OutputPaths: []string{logPath}, + ErrorOutputPaths: []string{logPath}, + } + gl, err := NewGRPCLoggerV2(lcfg) + if err != nil { + t.Fatal(err) + } + + // debug level is not enabled, + // so info level gRPC-side logging is discarded + gl.Info("etcd-logutil-1") + data, err := ioutil.ReadFile(logPath) + if err != nil { + t.Fatal(err) + } + if bytes.Contains(data, []byte("etcd-logutil-1")) { + t.Fatalf("unexpected line %q", string(data)) + } + + gl.Warning("etcd-logutil-2") + data, err = ioutil.ReadFile(logPath) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(data, []byte("etcd-logutil-2")) { + t.Fatalf("can't find data in log %q", string(data)) + } + if !bytes.Contains(data, []byte("logutil/zap_test.go:")) { + t.Fatalf("unexpected caller; %q", string(data)) + } +} + +func TestNewRaftLogger(t *testing.T) { + logPath := filepath.Join(os.TempDir(), fmt.Sprintf("test-log-%d", time.Now().UnixNano())) + defer os.RemoveAll(logPath) + + lcfg := zap.Config{ + Level: zap.NewAtomicLevelAt(zap.DebugLevel), + Development: false, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: zap.NewProductionEncoderConfig(), + OutputPaths: []string{logPath}, + ErrorOutputPaths: []string{logPath}, + } + gl, err := NewRaftLogger(lcfg) + if err != nil { + t.Fatal(err) + } + + gl.Info("etcd-logutil-1") + data, err := ioutil.ReadFile(logPath) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(data, []byte("etcd-logutil-1")) { + t.Fatalf("can't find data in log %q", string(data)) + } + + gl.Warning("etcd-logutil-2") + data, err = ioutil.ReadFile(logPath) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(data, []byte("etcd-logutil-2")) { + t.Fatalf("can't find data in log %q", string(data)) + } + if !bytes.Contains(data, []byte("logutil/zap_test.go:")) { + t.Fatalf("unexpected caller; %q", string(data)) + } +}