...

Source file src/go.etcd.io/etcd/server/v3/embed/config_logging.go

Documentation: go.etcd.io/etcd/server/v3/embed

     1  // Copyright 2018 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package embed
    16  
    17  import (
    18  	"crypto/tls"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/url"
    24  	"os"
    25  
    26  	"go.etcd.io/etcd/client/pkg/v3/logutil"
    27  	"go.uber.org/zap"
    28  	"go.uber.org/zap/zapcore"
    29  	"go.uber.org/zap/zapgrpc"
    30  	"google.golang.org/grpc"
    31  	"google.golang.org/grpc/grpclog"
    32  	"gopkg.in/natefinch/lumberjack.v2"
    33  )
    34  
    35  // GetLogger returns the logger.
    36  func (cfg Config) GetLogger() *zap.Logger {
    37  	cfg.loggerMu.RLock()
    38  	l := cfg.logger
    39  	cfg.loggerMu.RUnlock()
    40  	return l
    41  }
    42  
    43  // setupLogging initializes etcd logging.
    44  // Must be called after flag parsing or finishing configuring embed.Config.
    45  func (cfg *Config) setupLogging() error {
    46  	switch cfg.Logger {
    47  	case "capnslog": // removed in v3.5
    48  		return fmt.Errorf("--logger=capnslog is removed in v3.5")
    49  
    50  	case "zap":
    51  		if len(cfg.LogOutputs) == 0 {
    52  			cfg.LogOutputs = []string{DefaultLogOutput}
    53  		}
    54  		if len(cfg.LogOutputs) > 1 {
    55  			for _, v := range cfg.LogOutputs {
    56  				if v == DefaultLogOutput {
    57  					return fmt.Errorf("multi logoutput for %q is not supported yet", DefaultLogOutput)
    58  				}
    59  			}
    60  		}
    61  		if cfg.EnableLogRotation {
    62  			if err := setupLogRotation(cfg.LogOutputs, cfg.LogRotationConfigJSON); err != nil {
    63  				return err
    64  			}
    65  		}
    66  
    67  		outputPaths, errOutputPaths := make([]string, 0), make([]string, 0)
    68  		isJournal := false
    69  		for _, v := range cfg.LogOutputs {
    70  			switch v {
    71  			case DefaultLogOutput:
    72  				outputPaths = append(outputPaths, StdErrLogOutput)
    73  				errOutputPaths = append(errOutputPaths, StdErrLogOutput)
    74  
    75  			case JournalLogOutput:
    76  				isJournal = true
    77  
    78  			case StdErrLogOutput:
    79  				outputPaths = append(outputPaths, StdErrLogOutput)
    80  				errOutputPaths = append(errOutputPaths, StdErrLogOutput)
    81  
    82  			case StdOutLogOutput:
    83  				outputPaths = append(outputPaths, StdOutLogOutput)
    84  				errOutputPaths = append(errOutputPaths, StdOutLogOutput)
    85  
    86  			default:
    87  				var path string
    88  				if cfg.EnableLogRotation {
    89  					// append rotate scheme to logs managed by lumberjack log rotation
    90  					if v[0:1] == "/" {
    91  						path = fmt.Sprintf("rotate:/%%2F%s", v[1:])
    92  					} else {
    93  						path = fmt.Sprintf("rotate:/%s", v)
    94  					}
    95  				} else {
    96  					path = v
    97  				}
    98  				outputPaths = append(outputPaths, path)
    99  				errOutputPaths = append(errOutputPaths, path)
   100  			}
   101  		}
   102  
   103  		if !isJournal {
   104  			copied := logutil.DefaultZapLoggerConfig
   105  			copied.OutputPaths = outputPaths
   106  			copied.ErrorOutputPaths = errOutputPaths
   107  			copied = logutil.MergeOutputPaths(copied)
   108  			copied.Level = zap.NewAtomicLevelAt(logutil.ConvertToZapLevel(cfg.LogLevel))
   109  			if cfg.ZapLoggerBuilder == nil {
   110  				lg, err := copied.Build()
   111  				if err != nil {
   112  					return err
   113  				}
   114  				cfg.ZapLoggerBuilder = NewZapLoggerBuilder(lg)
   115  			}
   116  		} else {
   117  			if len(cfg.LogOutputs) > 1 {
   118  				for _, v := range cfg.LogOutputs {
   119  					if v != DefaultLogOutput {
   120  						return fmt.Errorf("running with systemd/journal but other '--log-outputs' values (%q) are configured with 'default'; override 'default' value with something else", cfg.LogOutputs)
   121  					}
   122  				}
   123  			}
   124  
   125  			// use stderr as fallback
   126  			syncer, lerr := getJournalWriteSyncer()
   127  			if lerr != nil {
   128  				return lerr
   129  			}
   130  
   131  			lvl := zap.NewAtomicLevelAt(logutil.ConvertToZapLevel(cfg.LogLevel))
   132  
   133  			// WARN: do not change field names in encoder config
   134  			// journald logging writer assumes field names of "level" and "caller"
   135  			cr := zapcore.NewCore(
   136  				zapcore.NewJSONEncoder(logutil.DefaultZapLoggerConfig.EncoderConfig),
   137  				syncer,
   138  				lvl,
   139  			)
   140  			if cfg.ZapLoggerBuilder == nil {
   141  				cfg.ZapLoggerBuilder = NewZapLoggerBuilder(zap.New(cr, zap.AddCaller(), zap.ErrorOutput(syncer)))
   142  			}
   143  		}
   144  
   145  		err := cfg.ZapLoggerBuilder(cfg)
   146  		if err != nil {
   147  			return err
   148  		}
   149  
   150  		logTLSHandshakeFailure := func(conn *tls.Conn, err error) {
   151  			state := conn.ConnectionState()
   152  			remoteAddr := conn.RemoteAddr().String()
   153  			serverName := state.ServerName
   154  			if len(state.PeerCertificates) > 0 {
   155  				cert := state.PeerCertificates[0]
   156  				ips := make([]string, len(cert.IPAddresses))
   157  				for i := range cert.IPAddresses {
   158  					ips[i] = cert.IPAddresses[i].String()
   159  				}
   160  				cfg.logger.Warn(
   161  					"rejected connection",
   162  					zap.String("remote-addr", remoteAddr),
   163  					zap.String("server-name", serverName),
   164  					zap.Strings("ip-addresses", ips),
   165  					zap.Strings("dns-names", cert.DNSNames),
   166  					zap.Error(err),
   167  				)
   168  			} else {
   169  				cfg.logger.Warn(
   170  					"rejected connection",
   171  					zap.String("remote-addr", remoteAddr),
   172  					zap.String("server-name", serverName),
   173  					zap.Error(err),
   174  				)
   175  			}
   176  		}
   177  		cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
   178  		cfg.PeerTLSInfo.HandshakeFailure = logTLSHandshakeFailure
   179  
   180  	default:
   181  		return fmt.Errorf("unknown logger option %q", cfg.Logger)
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // NewZapLoggerBuilder generates a zap logger builder that sets given loger
   188  // for embedded etcd.
   189  func NewZapLoggerBuilder(lg *zap.Logger) func(*Config) error {
   190  	return func(cfg *Config) error {
   191  		cfg.loggerMu.Lock()
   192  		defer cfg.loggerMu.Unlock()
   193  		cfg.logger = lg
   194  		return nil
   195  	}
   196  }
   197  
   198  // NewZapCoreLoggerBuilder - is a deprecated setter for the logger.
   199  // Deprecated: Use simpler NewZapLoggerBuilder. To be removed in etcd-3.6.
   200  func NewZapCoreLoggerBuilder(lg *zap.Logger, _ zapcore.Core, _ zapcore.WriteSyncer) func(*Config) error {
   201  	return NewZapLoggerBuilder(lg)
   202  }
   203  
   204  // SetupGlobalLoggers configures 'global' loggers (grpc, zapGlobal) based on the cfg.
   205  //
   206  // The method is not executed by embed server by default (since 3.5) to
   207  // enable setups where grpc/zap.Global logging is configured independently
   208  // or spans separate lifecycle (like in tests).
   209  func (cfg *Config) SetupGlobalLoggers() {
   210  	lg := cfg.GetLogger()
   211  	if lg != nil {
   212  		if cfg.LogLevel == "debug" {
   213  			grpc.EnableTracing = true
   214  			grpclog.SetLoggerV2(zapgrpc.NewLogger(lg))
   215  		} else {
   216  			grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
   217  		}
   218  		zap.ReplaceGlobals(lg)
   219  	}
   220  }
   221  
   222  type logRotationConfig struct {
   223  	*lumberjack.Logger
   224  }
   225  
   226  // Sync implements zap.Sink
   227  func (logRotationConfig) Sync() error { return nil }
   228  
   229  // setupLogRotation initializes log rotation for a single file path target.
   230  func setupLogRotation(logOutputs []string, logRotateConfigJSON string) error {
   231  	var logRotationConfig logRotationConfig
   232  	outputFilePaths := 0
   233  	for _, v := range logOutputs {
   234  		switch v {
   235  		case DefaultLogOutput, StdErrLogOutput, StdOutLogOutput:
   236  			continue
   237  		default:
   238  			outputFilePaths++
   239  		}
   240  	}
   241  	// log rotation requires file target
   242  	if len(logOutputs) == 1 && outputFilePaths == 0 {
   243  		return ErrLogRotationInvalidLogOutput
   244  	}
   245  	// support max 1 file target for log rotation
   246  	if outputFilePaths > 1 {
   247  		return ErrLogRotationInvalidLogOutput
   248  	}
   249  
   250  	if err := json.Unmarshal([]byte(logRotateConfigJSON), &logRotationConfig); err != nil {
   251  		var unmarshalTypeError *json.UnmarshalTypeError
   252  		var syntaxError *json.SyntaxError
   253  		switch {
   254  		case errors.As(err, &syntaxError):
   255  			return fmt.Errorf("improperly formatted log rotation config: %w", err)
   256  		case errors.As(err, &unmarshalTypeError):
   257  			return fmt.Errorf("invalid log rotation config: %w", err)
   258  		}
   259  	}
   260  	zap.RegisterSink("rotate", func(u *url.URL) (zap.Sink, error) {
   261  		logRotationConfig.Filename = u.Path[1:]
   262  		return &logRotationConfig, nil
   263  	})
   264  	return nil
   265  }
   266  

View as plain text