Browse Source

Merge pull request #10947 from gyuho/log-level

*: make log level configurable
Gyuho Lee 6 years ago
parent
commit
6e766ac5fb

+ 17 - 1
Documentation/op-guide/configuration.md

@@ -342,7 +342,8 @@ The security flags help to [build a secure etcd cluster][security].
 
 
 ### --logger
 ### --logger
 
 
-**Available from v3.4**
+**Available from v3.4.**
+**WARNING: `--logger=capnslog` to be deprecated in v3.5.**
 
 
 + Specify 'zap' for structured logging or 'capnslog'.
 + Specify 'zap' for structured logging or 'capnslog'.
 + default: capnslog
 + default: capnslog
@@ -354,12 +355,27 @@ The security flags help to [build a secure etcd cluster][security].
 + env variable: ETCD_LOG_OUTPUTS
 + env variable: ETCD_LOG_OUTPUTS
 + 'default' use 'stderr' config for v3.4 during zap logger migraion
 + 'default' use 'stderr' config for v3.4 during zap logger migraion
 
 
+### --log-level
+
+**Available from v3.4.**
+
++ Configures log level. Only supports debug, info, warn, error, panic, or fatal.
++ default: info
++ env variable: ETCD_LOG_LEVEL
++ 'default' use 'info'.
+
 ### --debug
 ### --debug
+
+**WARNING: to be deprecated in v3.5.**
+
 + Drop the default log level to DEBUG for all subpackages.
 + Drop the default log level to DEBUG for all subpackages.
 + default: false (INFO for all packages)
 + default: false (INFO for all packages)
 + env variable: ETCD_DEBUG
 + env variable: ETCD_DEBUG
 
 
 ### --log-package-levels
 ### --log-package-levels
+
+**WARNING: to be deprecated in v3.5.**
+
 + Set individual etcd subpackages to specific log levels. An example being `etcdserver=WARNING,security=DEBUG`
 + Set individual etcd subpackages to specific log levels. An example being `etcdserver=WARNING,security=DEBUG`
 + default: "" (INFO for all packages)
 + default: "" (INFO for all packages)
 + env variable: ETCD_LOG_PACKAGE_LEVELS
 + env variable: ETCD_LOG_PACKAGE_LEVELS

+ 12 - 10
embed/config.go

@@ -30,6 +30,7 @@ import (
 	"go.etcd.io/etcd/etcdserver"
 	"go.etcd.io/etcd/etcdserver"
 	"go.etcd.io/etcd/etcdserver/api/v3compactor"
 	"go.etcd.io/etcd/etcdserver/api/v3compactor"
 	"go.etcd.io/etcd/pkg/flags"
 	"go.etcd.io/etcd/pkg/flags"
+	"go.etcd.io/etcd/pkg/logutil"
 	"go.etcd.io/etcd/pkg/netutil"
 	"go.etcd.io/etcd/pkg/netutil"
 	"go.etcd.io/etcd/pkg/srv"
 	"go.etcd.io/etcd/pkg/srv"
 	"go.etcd.io/etcd/pkg/tlsutil"
 	"go.etcd.io/etcd/pkg/tlsutil"
@@ -291,11 +292,8 @@ type Config struct {
 	// Logger is logger options: "zap", "capnslog".
 	// Logger is logger options: "zap", "capnslog".
 	// WARN: "capnslog" is being deprecated in v3.5.
 	// WARN: "capnslog" is being deprecated in v3.5.
 	Logger string `json:"logger"`
 	Logger string `json:"logger"`
-
-	// DeprecatedLogOutput is to be deprecated in v3.5.
-	// Just here for safe migration in v3.4.
-	DeprecatedLogOutput []string `json:"log-output"`
-
+	// LogLevel configures log level. Only supports debug, info, warn, error, panic, or fatal. Default 'info'.
+	LogLevel string `json:"log-level"`
 	// LogOutputs is either:
 	// LogOutputs is either:
 	//  - "default" as os.Stderr,
 	//  - "default" as os.Stderr,
 	//  - "stderr" as os.Stderr,
 	//  - "stderr" as os.Stderr,
@@ -304,11 +302,8 @@ type Config struct {
 	// It can be multiple when "Logger" is zap.
 	// It can be multiple when "Logger" is zap.
 	LogOutputs []string `json:"log-outputs"`
 	LogOutputs []string `json:"log-outputs"`
 
 
-	// Debug is true, to enable debug level logging.
-	Debug bool `json:"debug"`
-
-	// ZapLoggerBuilder is used to build the zap logger.
-	ZapLoggerBuilder func(*Config) error
+	// zapLoggerBuilder is used to build the zap logger.
+	zapLoggerBuilder func(*Config) error
 
 
 	// logger logs server-side operations. The default is nil,
 	// logger logs server-side operations. The default is nil,
 	// and "setupLogging" must be called before starting server.
 	// and "setupLogging" must be called before starting server.
@@ -329,6 +324,12 @@ type Config struct {
 
 
 	// TO BE DEPRECATED
 	// TO BE DEPRECATED
 
 
+	// DeprecatedLogOutput is to be deprecated in v3.5.
+	// Just here for safe migration in v3.4.
+	DeprecatedLogOutput []string `json:"log-output"`
+	// Debug is true, to enable debug level logging.
+	// WARNING: to be deprecated in 3.5. Use "--log-level=debug" instead.
+	Debug bool `json:"debug"`
 	// LogPkgLevels is being deprecated in v3.5.
 	// LogPkgLevels is being deprecated in v3.5.
 	// Only valid if "logger" option is "capnslog".
 	// Only valid if "logger" option is "capnslog".
 	// WARN: DO NOT USE THIS!
 	// WARN: DO NOT USE THIS!
@@ -415,6 +416,7 @@ func NewConfig() *Config {
 		DeprecatedLogOutput: []string{DefaultLogOutput},
 		DeprecatedLogOutput: []string{DefaultLogOutput},
 		LogOutputs:          []string{DefaultLogOutput},
 		LogOutputs:          []string{DefaultLogOutput},
 		Debug:               false,
 		Debug:               false,
+		LogLevel:            logutil.DefaultLogLevel,
 		LogPkgLevels:        "",
 		LogPkgLevels:        "",
 	}
 	}
 	cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
 	cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)

+ 31 - 13
embed/config_logging.go

@@ -69,11 +69,25 @@ func (cfg *Config) setupLogging() error {
 			return fmt.Errorf("'--log-output=%q' and '--log-outputs=%q' are incompatible; only set --log-outputs", cfg.DeprecatedLogOutput, cfg.LogOutputs)
 			return fmt.Errorf("'--log-output=%q' and '--log-outputs=%q' are incompatible; only set --log-outputs", cfg.DeprecatedLogOutput, cfg.LogOutputs)
 		}
 		}
 		if !reflect.DeepEqual(cfg.DeprecatedLogOutput, []string{DefaultLogOutput}) {
 		if !reflect.DeepEqual(cfg.DeprecatedLogOutput, []string{DefaultLogOutput}) {
-			fmt.Fprintf(os.Stderr, "Deprecated '--log-output' flag is set to %q\n", cfg.DeprecatedLogOutput)
+			fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--log-output' flag is set to %q\n", cfg.DeprecatedLogOutput)
 			fmt.Fprintln(os.Stderr, "Please use '--log-outputs' flag")
 			fmt.Fprintln(os.Stderr, "Please use '--log-outputs' flag")
 		}
 		}
 	}
 	}
 
 
+	// TODO: remove after deprecating log related flags in v3.5
+	if cfg.Debug {
+		fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--debug' flag is set to %v (use '--log-level=debug' instead\n", cfg.Debug)
+	}
+	if cfg.Debug && cfg.LogLevel != "debug" {
+		fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--debug' flag is set to %v with inconsistent '--log-level=%s' flag\n", cfg.Debug, cfg.LogLevel)
+	}
+	if cfg.Logger == "capnslog" {
+		fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--logger=%s' flag is set; use '--logger=zap' flag instead\n", cfg.Logger)
+	}
+	if cfg.LogPkgLevels != "" {
+		fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--log-package-levels=%s' flag is set; use '--logger=zap' flag instead\n", cfg.LogPkgLevels)
+	}
+
 	switch cfg.Logger {
 	switch cfg.Logger {
 	case "capnslog": // TODO: deprecate this in v3.5
 	case "capnslog": // TODO: deprecate this in v3.5
 		cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
 		cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
@@ -85,7 +99,7 @@ func (cfg *Config) setupLogging() error {
 			// enable info, warning, error
 			// enable info, warning, error
 			grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
 			grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
 		} else {
 		} else {
-			capnslog.SetGlobalLogLevel(capnslog.INFO)
+			capnslog.SetGlobalLogLevel(logutil.ConvertToCapnslogLogLevel(cfg.LogLevel))
 			// only discard info
 			// only discard info
 			grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
 			grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
 		}
 		}
@@ -157,13 +171,15 @@ func (cfg *Config) setupLogging() error {
 
 
 		if !isJournal {
 		if !isJournal {
 			copied := logutil.AddOutputPaths(logutil.DefaultZapLoggerConfig, outputPaths, errOutputPaths)
 			copied := logutil.AddOutputPaths(logutil.DefaultZapLoggerConfig, outputPaths, errOutputPaths)
-
-			if cfg.Debug {
-				copied.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
+			copied.Level = zap.NewAtomicLevelAt(logutil.ConvertToZapLevel(cfg.LogLevel))
+			if cfg.Debug || cfg.LogLevel == "debug" {
+				// enable tracing even when "--debug --log-level info"
+				// in order to keep backward compatibility with <= v3.3
+				// TODO: remove "Debug" check in v3.5
 				grpc.EnableTracing = true
 				grpc.EnableTracing = true
 			}
 			}
-			if cfg.ZapLoggerBuilder == nil {
-				cfg.ZapLoggerBuilder = func(c *Config) error {
+			if cfg.zapLoggerBuilder == nil {
+				cfg.zapLoggerBuilder = func(c *Config) error {
 					var err error
 					var err error
 					c.logger, err = copied.Build()
 					c.logger, err = copied.Build()
 					if err != nil {
 					if err != nil {
@@ -201,9 +217,11 @@ func (cfg *Config) setupLogging() error {
 				return lerr
 				return lerr
 			}
 			}
 
 
-			lvl := zap.NewAtomicLevelAt(zap.InfoLevel)
-			if cfg.Debug {
-				lvl = zap.NewAtomicLevelAt(zap.DebugLevel)
+			lvl := zap.NewAtomicLevelAt(logutil.ConvertToZapLevel(cfg.LogLevel))
+			if cfg.Debug || cfg.LogLevel == "debug" {
+				// enable tracing even when "--debug --log-level info"
+				// in order to keep backward compatibility with <= v3.3
+				// TODO: remove "Debug" check in v3.5
 				grpc.EnableTracing = true
 				grpc.EnableTracing = true
 			}
 			}
 
 
@@ -214,8 +232,8 @@ func (cfg *Config) setupLogging() error {
 				syncer,
 				syncer,
 				lvl,
 				lvl,
 			)
 			)
-			if cfg.ZapLoggerBuilder == nil {
-				cfg.ZapLoggerBuilder = func(c *Config) error {
+			if cfg.zapLoggerBuilder == nil {
+				cfg.zapLoggerBuilder = func(c *Config) error {
 					c.logger = zap.New(cr, zap.AddCaller(), zap.ErrorOutput(syncer))
 					c.logger = zap.New(cr, zap.AddCaller(), zap.ErrorOutput(syncer))
 					c.loggerMu.Lock()
 					c.loggerMu.Lock()
 					defer c.loggerMu.Unlock()
 					defer c.loggerMu.Unlock()
@@ -231,7 +249,7 @@ func (cfg *Config) setupLogging() error {
 			}
 			}
 		}
 		}
 
 
-		err := cfg.ZapLoggerBuilder(cfg)
+		err := cfg.zapLoggerBuilder(cfg)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}

+ 6 - 4
etcdmain/config.go

@@ -28,6 +28,7 @@ import (
 
 
 	"go.etcd.io/etcd/embed"
 	"go.etcd.io/etcd/embed"
 	"go.etcd.io/etcd/pkg/flags"
 	"go.etcd.io/etcd/pkg/flags"
+	"go.etcd.io/etcd/pkg/logutil"
 	"go.etcd.io/etcd/pkg/types"
 	"go.etcd.io/etcd/pkg/types"
 	"go.etcd.io/etcd/version"
 	"go.etcd.io/etcd/version"
 
 
@@ -221,11 +222,12 @@ func newConfig() *config {
 	fs.Var(flags.NewUniqueStringsValue("*"), "host-whitelist", "Comma-separated acceptable hostnames from HTTP client requests, if server is not secure (empty means allow all).")
 	fs.Var(flags.NewUniqueStringsValue("*"), "host-whitelist", "Comma-separated acceptable hostnames from HTTP client requests, if server is not secure (empty means allow all).")
 
 
 	// logging
 	// logging
-	fs.StringVar(&cfg.ec.Logger, "logger", "capnslog", "Specify 'zap' for structured logging or 'capnslog'.")
-	fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-output", "DEPRECATED: use '--log-outputs'.")
+	fs.StringVar(&cfg.ec.Logger, "logger", "capnslog", "Specify 'zap' for structured logging or 'capnslog'. WARN: 'capnslog' is being deprecated in v3.5.")
+	fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-output", "[TO BE DEPRECATED IN v3.5] use '--log-outputs'.")
 	fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-outputs", "Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.")
 	fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-outputs", "Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.")
-	fs.BoolVar(&cfg.ec.Debug, "debug", false, "Enable debug-level logging for etcd.")
-	fs.StringVar(&cfg.ec.LogPkgLevels, "log-package-levels", "", "(To be deprecated) Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').")
+	fs.BoolVar(&cfg.ec.Debug, "debug", false, "[TO BE DEPRECATED IN v3.5] Enable debug-level logging for etcd. Use '--log-level=debug' instead.")
+	fs.StringVar(&cfg.ec.LogLevel, "log-level", logutil.DefaultLogLevel, "Configures log level. Only supports debug, info, warn, error, panic, or fatal. Default 'info'.")
+	fs.StringVar(&cfg.ec.LogPkgLevels, "log-package-levels", "", "[TO BE DEPRECATED IN v3.5] Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').")
 
 
 	// version
 	// version
 	fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.")
 	fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.")

+ 10 - 7
etcdmain/help.go

@@ -173,15 +173,11 @@ Profiling and Monitoring:
 
 
 Logging:
 Logging:
   --logger 'capnslog'
   --logger 'capnslog'
-    Specify 'zap' for structured logging or 'capnslog'.
+    Specify 'zap' for structured logging or 'capnslog'. [WARN] 'capnslog' will be deprecated in v3.5.
   --log-outputs 'default'
   --log-outputs 'default'
     Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.
     Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.
-  --debug 'false'
-    Enable debug-level logging for etcd.
-
-Logging (to be deprecated in v3.5):
-  --log-package-levels ''
-    Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').
+  --log-level 'info'
+    Configures log level. Only supports debug, info, warn, error, panic, or fatal.
 
 
 v2 Proxy (to be deprecated in v4):
 v2 Proxy (to be deprecated in v4):
   --proxy 'off'
   --proxy 'off'
@@ -214,5 +210,12 @@ Unsafe feature:
     Force to create a new one-member cluster.
     Force to create a new one-member cluster.
 
 
 CAUTIOUS with unsafe flag! It may break the guarantees given by the consensus protocol!
 CAUTIOUS with unsafe flag! It may break the guarantees given by the consensus protocol!
+
+TO BE DEPRECATED:
+
+  --debug 'false'
+    Enable debug-level logging for etcd. [WARN] Will be deprecated in v3.5. Use '--log-level=debug' instead.
+  --log-package-levels ''
+    Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').
 `
 `
 )
 )

+ 12 - 7
etcdserver/raft.go

@@ -58,13 +58,6 @@ var (
 )
 )
 
 
 func init() {
 func init() {
-	lcfg := logutil.DefaultZapLoggerConfig
-	lg, err := logutil.NewRaftLogger(&lcfg)
-	if err != nil {
-		log.Fatalf("cannot create raft logger %v", err)
-	}
-	raft.SetLogger(lg)
-
 	expvar.Publish("raft.status", expvar.Func(func() interface{} {
 	expvar.Publish("raft.status", expvar.Func(func() interface{} {
 		raftStatusMu.Lock()
 		raftStatusMu.Lock()
 		defer raftStatusMu.Unlock()
 		defer raftStatusMu.Unlock()
@@ -124,6 +117,18 @@ type raftNodeConfig struct {
 }
 }
 
 
 func newRaftNode(cfg raftNodeConfig) *raftNode {
 func newRaftNode(cfg raftNodeConfig) *raftNode {
+	var lg raft.Logger
+	if cfg.lg != nil {
+		lg = logutil.NewRaftLoggerZap(cfg.lg)
+	} else {
+		lcfg := logutil.DefaultZapLoggerConfig
+		var err error
+		lg, err = logutil.NewRaftLogger(&lcfg)
+		if err != nil {
+			log.Fatalf("cannot create raft logger %v", err)
+		}
+	}
+	raft.SetLogger(lg)
 	r := &raftNode{
 	r := &raftNode{
 		lg:             cfg.lg,
 		lg:             cfg.lg,
 		tickMu:         new(sync.Mutex),
 		tickMu:         new(sync.Mutex),

+ 70 - 0
pkg/logutil/log_level.go

@@ -0,0 +1,70 @@
+// Copyright 2019 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 (
+	"fmt"
+
+	"github.com/coreos/pkg/capnslog"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+var DefaultLogLevel = "info"
+
+// ConvertToZapLevel converts log level string to zapcore.Level.
+func ConvertToZapLevel(lvl string) zapcore.Level {
+	switch lvl {
+	case "debug":
+		return zap.DebugLevel
+	case "info":
+		return zap.InfoLevel
+	case "warn":
+		return zap.WarnLevel
+	case "error":
+		return zap.ErrorLevel
+	case "dpanic":
+		return zap.DPanicLevel
+	case "panic":
+		return zap.PanicLevel
+	case "fatal":
+		return zap.FatalLevel
+	default:
+		panic(fmt.Sprintf("unknown level %q", lvl))
+	}
+}
+
+// ConvertToCapnslogLogLevel convert log level string to capnslog.LogLevel.
+// TODO: deprecate this in 3.5
+func ConvertToCapnslogLogLevel(lvl string) capnslog.LogLevel {
+	switch lvl {
+	case "debug":
+		return capnslog.DEBUG
+	case "info":
+		return capnslog.INFO
+	case "warn":
+		return capnslog.WARNING
+	case "error":
+		return capnslog.ERROR
+	case "dpanic":
+		return capnslog.CRITICAL
+	case "panic":
+		return capnslog.CRITICAL
+	case "fatal":
+		return capnslog.CRITICAL
+	default:
+		panic(fmt.Sprintf("unknown level %q", lvl))
+	}
+}

+ 1 - 1
pkg/logutil/zap.go

@@ -23,7 +23,7 @@ import (
 
 
 // DefaultZapLoggerConfig defines default zap logger configuration.
 // DefaultZapLoggerConfig defines default zap logger configuration.
 var DefaultZapLoggerConfig = zap.Config{
 var DefaultZapLoggerConfig = zap.Config{
-	Level: zap.NewAtomicLevelAt(zap.InfoLevel),
+	Level: zap.NewAtomicLevelAt(ConvertToZapLevel(DefaultLogLevel)),
 
 
 	Development: false,
 	Development: false,
 	Sampling: &zap.SamplingConfig{
 	Sampling: &zap.SamplingConfig{

+ 6 - 1
pkg/logutil/zap_raft.go

@@ -23,7 +23,7 @@ import (
 	"go.uber.org/zap/zapcore"
 	"go.uber.org/zap/zapcore"
 )
 )
 
 
-// NewRaftLogger converts "*zap.Logger" to "raft.Logger".
+// NewRaftLogger builds "raft.Logger" from "*zap.Config".
 func NewRaftLogger(lcfg *zap.Config) (raft.Logger, error) {
 func NewRaftLogger(lcfg *zap.Config) (raft.Logger, error) {
 	if lcfg == nil {
 	if lcfg == nil {
 		return nil, errors.New("nil zap.Config")
 		return nil, errors.New("nil zap.Config")
@@ -35,6 +35,11 @@ func NewRaftLogger(lcfg *zap.Config) (raft.Logger, error) {
 	return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}, nil
 	return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}, nil
 }
 }
 
 
+// NewRaftLoggerZap converts "*zap.Logger" to "raft.Logger".
+func NewRaftLoggerZap(lg *zap.Logger) raft.Logger {
+	return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}
+}
+
 // NewRaftLoggerFromZapCore creates "raft.Logger" from "zap.Core"
 // NewRaftLoggerFromZapCore creates "raft.Logger" from "zap.Core"
 // and "zapcore.WriteSyncer".
 // and "zapcore.WriteSyncer".
 func NewRaftLoggerFromZapCore(cr zapcore.Core, syncer zapcore.WriteSyncer) raft.Logger {
 func NewRaftLoggerFromZapCore(cr zapcore.Core, syncer zapcore.WriteSyncer) raft.Logger {

+ 7 - 1
raft/logger.go

@@ -19,6 +19,7 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"log"
 	"log"
 	"os"
 	"os"
+	"sync"
 )
 )
 
 
 type Logger interface {
 type Logger interface {
@@ -41,11 +42,16 @@ type Logger interface {
 	Panicf(format string, v ...interface{})
 	Panicf(format string, v ...interface{})
 }
 }
 
 
-func SetLogger(l Logger) { raftLogger = l }
+func SetLogger(l Logger) {
+	raftLoggerMu.Lock()
+	raftLogger = l
+	raftLoggerMu.Unlock()
+}
 
 
 var (
 var (
 	defaultLogger = &DefaultLogger{Logger: log.New(os.Stderr, "raft", log.LstdFlags)}
 	defaultLogger = &DefaultLogger{Logger: log.New(os.Stderr, "raft", log.LstdFlags)}
 	discardLogger = &DefaultLogger{Logger: log.New(ioutil.Discard, "", 0)}
 	discardLogger = &DefaultLogger{Logger: log.New(ioutil.Discard, "", 0)}
+	raftLoggerMu  sync.Mutex
 	raftLogger    = Logger(defaultLogger)
 	raftLogger    = Logger(defaultLogger)
 )
 )