2018-04-27 00:22:33 +03:00
// 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 embed
import (
"crypto/tls"
"fmt"
"sync"
2019-05-29 00:59:02 +03:00
"go.etcd.io/etcd/pkg/logutil"
2018-04-27 00:22:33 +03:00
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
// GetLogger returns the logger.
func ( cfg Config ) GetLogger ( ) * zap . Logger {
cfg . loggerMu . RLock ( )
l := cfg . logger
cfg . loggerMu . RUnlock ( )
return l
}
// for testing
var grpcLogOnce = new ( sync . Once )
// setupLogging initializes etcd logging.
// Must be called after flag parsing or finishing configuring embed.Config.
func ( cfg * Config ) setupLogging ( ) error {
switch cfg . Logger {
2020-02-03 18:35:47 +03:00
case "capnslog" : // removed in v3.5
return fmt . Errorf ( "--logger=capnslog is removed in v3.5" )
2018-04-27 00:22:33 +03:00
case "zap" :
if len ( cfg . LogOutputs ) == 0 {
cfg . LogOutputs = [ ] string { DefaultLogOutput }
}
if len ( cfg . LogOutputs ) > 1 {
for _ , v := range cfg . LogOutputs {
if v == DefaultLogOutput {
config: multiple logging fixes
First, don't panic with invalid --log-outputs. For example:
$> ./bin/etcd --log-outputs foo
2018-12-20 15:05:47.988652 C | embed: unknown log-output "foo" (only supports "default", "stderr", "stdout")
panic: unknown log-output "foo" (only supports "default", "stderr", "stdout")
goroutine 1 [running]:
go.etcd.io/etcd/vendor/github.com/coreos/pkg/capnslog.(*PackageLogger).Panicf(0xc000294b00, 0x10fe067, 0x30, 0xc0001fa398, 0x4, 0x4)
go.etcd.io/etcd/vendor/github.com/coreos/pkg/capnslog/pkg_logger.go:75 +0x161
go.etcd.io/etcd/embed.(*Config).setupLogging(0xc000291400, 0xc0002a85b0, 0x1)
go.etcd.io/etcd/embed/config_logging.go:120 +0x1939
...
Or:
$> ./bin/etcd --log-outputs foo,default --logger zap
panic: multi logoutput for "default" is not supported yet
goroutine 1 [running]:
go.etcd.io/etcd/embed.(*Config).setupLogging(0xc000314500, 0xc0001b2f70, 0x1)
go.etcd.io/etcd/embed/config_logging.go:129 +0x2437
go.etcd.io/etcd/embed.(*Config).Validate(0xc000314500, 0xc000268a98, 0x127e440)
go.etcd.io/etcd/embed/config.go:543 +0x43
Second, don't exit in embed.setupLogging(). Before:
$> ./bin/etcd --log-outputs foo,bar
--logger=capnslog supports only 1 value in '--log-outputs', got ["bar" "foo"]
and after:
$> ./bin/etcd --log-outputs foo,bar
2018-12-20 15:10:24.317982 E | etcdmain: error verifying flags, --logger=capnslog supports only 1 value in '--log-outputs', got ["bar" "foo"]. See 'etcd --help'.
Third, remove duplicated unique strings code. UniqueStringsFromFlag()
is already available to return a sorted slice of values, so just use
that.
Lastly, fix a tiny logging typo in config.
2019-01-14 16:09:36 +03:00
return fmt . Errorf ( "multi logoutput for %q is not supported yet" , DefaultLogOutput )
2018-04-27 00:22:33 +03:00
}
}
}
2019-02-21 21:57:26 +03:00
outputPaths , errOutputPaths := make ( [ ] string , 0 ) , make ( [ ] string , 0 )
2018-05-17 00:47:43 +03:00
isJournal := false
2018-04-27 00:22:33 +03:00
for _ , v := range cfg . LogOutputs {
switch v {
case DefaultLogOutput :
2019-02-21 21:57:26 +03:00
outputPaths = append ( outputPaths , StdErrLogOutput )
errOutputPaths = append ( errOutputPaths , StdErrLogOutput )
2018-04-27 00:22:33 +03:00
2018-05-17 00:47:43 +03:00
case JournalLogOutput :
isJournal = true
2018-04-27 00:22:33 +03:00
2018-05-17 00:47:43 +03:00
case StdErrLogOutput :
2019-02-21 21:57:26 +03:00
outputPaths = append ( outputPaths , StdErrLogOutput )
errOutputPaths = append ( errOutputPaths , StdErrLogOutput )
2018-04-27 00:22:33 +03:00
2018-05-17 00:47:43 +03:00
case StdOutLogOutput :
2019-02-21 21:57:26 +03:00
outputPaths = append ( outputPaths , StdOutLogOutput )
errOutputPaths = append ( errOutputPaths , StdOutLogOutput )
2018-04-27 00:22:33 +03:00
default :
2019-02-21 21:57:26 +03:00
outputPaths = append ( outputPaths , v )
errOutputPaths = append ( errOutputPaths , v )
2018-04-27 00:22:33 +03:00
}
}
2018-05-17 00:47:43 +03:00
if ! isJournal {
2019-08-27 08:45:48 +03:00
copied := logutil . DefaultZapLoggerConfig
copied . OutputPaths = outputPaths
copied . ErrorOutputPaths = errOutputPaths
copied = logutil . MergeOutputPaths ( copied )
2019-07-29 05:37:36 +03:00
copied . Level = zap . NewAtomicLevelAt ( logutil . ConvertToZapLevel ( cfg . LogLevel ) )
2020-02-03 18:35:47 +03:00
if cfg . LogLevel == "debug" {
2018-04-27 00:22:33 +03:00
grpc . EnableTracing = true
}
2019-09-13 14:46:01 +03:00
if cfg . ZapLoggerBuilder == nil {
cfg . ZapLoggerBuilder = func ( c * Config ) error {
2019-01-15 12:42:24 +03:00
var err error
2019-02-21 21:57:26 +03:00
c . logger , err = copied . Build ( )
2019-01-15 12:42:24 +03:00
if err != nil {
return err
}
c . loggerMu . Lock ( )
defer c . loggerMu . Unlock ( )
2019-02-21 21:57:26 +03:00
c . loggerConfig = & copied
2019-01-15 12:42:24 +03:00
c . loggerCore = nil
c . loggerWriteSyncer = nil
grpcLogOnce . Do ( func ( ) {
// debug true, enable info, warning, error
// debug false, only discard info
var gl grpclog . LoggerV2
2019-02-21 21:57:26 +03:00
gl , err = logutil . NewGRPCLoggerV2 ( copied )
2019-01-15 12:42:24 +03:00
if err == nil {
grpclog . SetLoggerV2 ( gl )
}
} )
return nil
2018-04-27 00:22:33 +03:00
}
}
} else {
if len ( cfg . LogOutputs ) > 1 {
for _ , v := range cfg . LogOutputs {
if v != DefaultLogOutput {
2018-05-17 00:47:43 +03:00
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 )
2018-04-27 00:22:33 +03:00
}
}
}
// use stderr as fallback
2018-05-17 00:25:10 +03:00
syncer , lerr := getJournalWriteSyncer ( )
if lerr != nil {
return lerr
}
2019-07-29 05:37:36 +03:00
lvl := zap . NewAtomicLevelAt ( logutil . ConvertToZapLevel ( cfg . LogLevel ) )
2020-02-03 18:35:47 +03:00
if cfg . LogLevel == "debug" {
2018-04-27 00:22:33 +03:00
grpc . EnableTracing = true
}
// WARN: do not change field names in encoder config
// journald logging writer assumes field names of "level" and "caller"
cr := zapcore . NewCore (
2019-02-21 21:57:26 +03:00
zapcore . NewJSONEncoder ( logutil . DefaultZapLoggerConfig . EncoderConfig ) ,
2018-04-27 00:22:33 +03:00
syncer ,
lvl ,
)
2019-09-13 14:46:01 +03:00
if cfg . ZapLoggerBuilder == nil {
cfg . ZapLoggerBuilder = func ( c * Config ) error {
2019-01-15 12:42:24 +03:00
c . logger = zap . New ( cr , zap . AddCaller ( ) , zap . ErrorOutput ( syncer ) )
c . loggerMu . Lock ( )
defer c . loggerMu . Unlock ( )
c . loggerConfig = nil
c . loggerCore = cr
c . loggerWriteSyncer = syncer
grpcLogOnce . Do ( func ( ) {
grpclog . SetLoggerV2 ( logutil . NewGRPCLoggerV2FromZapCore ( cr , syncer ) )
} )
return nil
}
}
}
2019-02-21 21:57:26 +03:00
2019-09-13 14:46:01 +03:00
err := cfg . ZapLoggerBuilder ( cfg )
2019-01-15 12:42:24 +03:00
if err != nil {
return err
2018-04-27 00:22:33 +03:00
}
2019-02-21 21:57:26 +03:00
2018-04-27 00:22:33 +03:00
logTLSHandshakeFailure := func ( conn * tls . Conn , err error ) {
state := conn . ConnectionState ( )
remoteAddr := conn . RemoteAddr ( ) . String ( )
serverName := state . ServerName
if len ( state . PeerCertificates ) > 0 {
cert := state . PeerCertificates [ 0 ]
2019-07-25 16:42:42 +03:00
ips := make ( [ ] string , len ( cert . IPAddresses ) )
2018-04-27 00:22:33 +03:00
for i := range cert . IPAddresses {
ips [ i ] = cert . IPAddresses [ i ] . String ( )
}
cfg . logger . Warn (
"rejected connection" ,
zap . String ( "remote-addr" , remoteAddr ) ,
zap . String ( "server-name" , serverName ) ,
zap . Strings ( "ip-addresses" , ips ) ,
zap . Strings ( "dns-names" , cert . DNSNames ) ,
zap . Error ( err ) ,
)
} else {
cfg . logger . Warn (
"rejected connection" ,
zap . String ( "remote-addr" , remoteAddr ) ,
zap . String ( "server-name" , serverName ) ,
zap . Error ( err ) ,
)
}
}
cfg . ClientTLSInfo . HandshakeFailure = logTLSHandshakeFailure
cfg . PeerTLSInfo . HandshakeFailure = logTLSHandshakeFailure
default :
return fmt . Errorf ( "unknown logger option %q" , cfg . Logger )
}
return nil
}
2019-01-15 12:42:24 +03:00
// NewZapCoreLoggerBuilder generates a zap core logger builder.
func NewZapCoreLoggerBuilder ( lg * zap . Logger , cr zapcore . Core , syncer zapcore . WriteSyncer ) func ( * Config ) error {
return func ( cfg * Config ) error {
cfg . loggerMu . Lock ( )
defer cfg . loggerMu . Unlock ( )
cfg . logger = lg
cfg . loggerConfig = nil
cfg . loggerCore = cr
cfg . loggerWriteSyncer = syncer
grpcLogOnce . Do ( func ( ) {
grpclog . SetLoggerV2 ( logutil . NewGRPCLoggerV2FromZapCore ( cr , syncer ) )
} )
return nil
}
}