blob: e42103cb18c2dbb9e332c6f439ec35046b139e14 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// 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
15package embed
16
17import (
18 "crypto/tls"
19 "errors"
20 "fmt"
21 "io/ioutil"
22 "os"
23 "reflect"
24 "sync"
25
26 "go.etcd.io/etcd/pkg/logutil"
27
28 "github.com/coreos/pkg/capnslog"
29 "go.uber.org/zap"
30 "go.uber.org/zap/zapcore"
31 "google.golang.org/grpc"
32 "google.golang.org/grpc/grpclog"
33)
34
35// GetLogger returns the logger.
36func (cfg Config) GetLogger() *zap.Logger {
37 cfg.loggerMu.RLock()
38 l := cfg.logger
39 cfg.loggerMu.RUnlock()
40 return l
41}
42
43// for testing
44var grpcLogOnce = new(sync.Once)
45
46// setupLogging initializes etcd logging.
47// Must be called after flag parsing or finishing configuring embed.Config.
48func (cfg *Config) setupLogging() error {
49 // handle "DeprecatedLogOutput" in v3.4
50 // TODO: remove "DeprecatedLogOutput" in v3.5
51 len1 := len(cfg.DeprecatedLogOutput)
52 len2 := len(cfg.LogOutputs)
53 if len1 != len2 {
54 switch {
55 case len1 > len2: // deprecate "log-output" flag is used
56 fmt.Fprintln(os.Stderr, "'--log-output' flag has been deprecated! Please use '--log-outputs'!")
57 cfg.LogOutputs = cfg.DeprecatedLogOutput
58 case len1 < len2: // "--log-outputs" flag has been set with multiple writers
59 cfg.DeprecatedLogOutput = []string{}
60 }
61 } else {
62 if len1 > 1 {
63 return errors.New("both '--log-output' and '--log-outputs' are set; only set '--log-outputs'")
64 }
65 if len1 < 1 {
66 return errors.New("either '--log-output' or '--log-outputs' flag must be set")
67 }
68 if reflect.DeepEqual(cfg.DeprecatedLogOutput, cfg.LogOutputs) && cfg.DeprecatedLogOutput[0] != DefaultLogOutput {
69 return fmt.Errorf("'--log-output=%q' and '--log-outputs=%q' are incompatible; only set --log-outputs", cfg.DeprecatedLogOutput, cfg.LogOutputs)
70 }
71 if !reflect.DeepEqual(cfg.DeprecatedLogOutput, []string{DefaultLogOutput}) {
72 fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--log-output' flag is set to %q\n", cfg.DeprecatedLogOutput)
73 fmt.Fprintln(os.Stderr, "Please use '--log-outputs' flag")
74 }
75 }
76
77 // TODO: remove after deprecating log related flags in v3.5
78 if cfg.Debug {
79 fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--debug' flag is set to %v (use '--log-level=debug' instead\n", cfg.Debug)
80 }
81 if cfg.Debug && cfg.LogLevel != "debug" {
82 fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--debug' flag is set to %v with inconsistent '--log-level=%s' flag\n", cfg.Debug, cfg.LogLevel)
83 }
84 if cfg.Logger == "capnslog" {
85 fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--logger=%s' flag is set; use '--logger=zap' flag instead\n", cfg.Logger)
86 }
87 if cfg.LogPkgLevels != "" {
88 fmt.Fprintf(os.Stderr, "[WARNING] Deprecated '--log-package-levels=%s' flag is set; use '--logger=zap' flag instead\n", cfg.LogPkgLevels)
89 }
90
91 switch cfg.Logger {
92 case "capnslog": // TODO: deprecate this in v3.5
93 cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
94 cfg.PeerTLSInfo.HandshakeFailure = logTLSHandshakeFailure
95
96 if cfg.Debug {
97 capnslog.SetGlobalLogLevel(capnslog.DEBUG)
98 grpc.EnableTracing = true
99 // enable info, warning, error
100 grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
101 } else {
102 capnslog.SetGlobalLogLevel(logutil.ConvertToCapnslogLogLevel(cfg.LogLevel))
103 // only discard info
104 grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
105 }
106
107 // TODO: deprecate with "capnslog"
108 if cfg.LogPkgLevels != "" {
109 repoLog := capnslog.MustRepoLogger("go.etcd.io/etcd")
110 settings, err := repoLog.ParseLogLevelConfig(cfg.LogPkgLevels)
111 if err != nil {
112 plog.Warningf("couldn't parse log level string: %s, continuing with default levels", err.Error())
113 return nil
114 }
115 repoLog.SetLogLevel(settings)
116 }
117
118 if len(cfg.LogOutputs) != 1 {
119 return fmt.Errorf("--logger=capnslog supports only 1 value in '--log-outputs', got %q", cfg.LogOutputs)
120 }
121 // capnslog initially SetFormatter(NewDefaultFormatter(os.Stderr))
122 // where NewDefaultFormatter returns NewJournaldFormatter when syscall.Getppid() == 1
123 // specify 'stdout' or 'stderr' to skip journald logging even when running under systemd
124 output := cfg.LogOutputs[0]
125 switch output {
126 case StdErrLogOutput:
127 capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stderr, cfg.Debug))
128 case StdOutLogOutput:
129 capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, cfg.Debug))
130 case DefaultLogOutput:
131 default:
132 return fmt.Errorf("unknown log-output %q (only supports %q, %q, %q)", output, DefaultLogOutput, StdErrLogOutput, StdOutLogOutput)
133 }
134
135 case "zap":
136 if len(cfg.LogOutputs) == 0 {
137 cfg.LogOutputs = []string{DefaultLogOutput}
138 }
139 if len(cfg.LogOutputs) > 1 {
140 for _, v := range cfg.LogOutputs {
141 if v == DefaultLogOutput {
142 return fmt.Errorf("multi logoutput for %q is not supported yet", DefaultLogOutput)
143 }
144 }
145 }
146
147 outputPaths, errOutputPaths := make([]string, 0), make([]string, 0)
148 isJournal := false
149 for _, v := range cfg.LogOutputs {
150 switch v {
151 case DefaultLogOutput:
152 outputPaths = append(outputPaths, StdErrLogOutput)
153 errOutputPaths = append(errOutputPaths, StdErrLogOutput)
154
155 case JournalLogOutput:
156 isJournal = true
157
158 case StdErrLogOutput:
159 outputPaths = append(outputPaths, StdErrLogOutput)
160 errOutputPaths = append(errOutputPaths, StdErrLogOutput)
161
162 case StdOutLogOutput:
163 outputPaths = append(outputPaths, StdOutLogOutput)
164 errOutputPaths = append(errOutputPaths, StdOutLogOutput)
165
166 default:
167 outputPaths = append(outputPaths, v)
168 errOutputPaths = append(errOutputPaths, v)
169 }
170 }
171
172 if !isJournal {
173 copied := logutil.DefaultZapLoggerConfig
174 copied.OutputPaths = outputPaths
175 copied.ErrorOutputPaths = errOutputPaths
176 copied = logutil.MergeOutputPaths(copied)
177 copied.Level = zap.NewAtomicLevelAt(logutil.ConvertToZapLevel(cfg.LogLevel))
178 if cfg.Debug || cfg.LogLevel == "debug" {
179 // enable tracing even when "--debug --log-level info"
180 // in order to keep backward compatibility with <= v3.3
181 // TODO: remove "Debug" check in v3.5
182 grpc.EnableTracing = true
183 }
184 if cfg.ZapLoggerBuilder == nil {
185 cfg.ZapLoggerBuilder = func(c *Config) error {
186 var err error
187 c.logger, err = copied.Build()
188 if err != nil {
189 return err
190 }
191 c.loggerMu.Lock()
192 defer c.loggerMu.Unlock()
193 c.loggerConfig = &copied
194 c.loggerCore = nil
195 c.loggerWriteSyncer = nil
196 grpcLogOnce.Do(func() {
197 // debug true, enable info, warning, error
198 // debug false, only discard info
199 var gl grpclog.LoggerV2
200 gl, err = logutil.NewGRPCLoggerV2(copied)
201 if err == nil {
202 grpclog.SetLoggerV2(gl)
203 }
204 })
205 return nil
206 }
207 }
208 } else {
209 if len(cfg.LogOutputs) > 1 {
210 for _, v := range cfg.LogOutputs {
211 if v != DefaultLogOutput {
212 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)
213 }
214 }
215 }
216
217 // use stderr as fallback
218 syncer, lerr := getJournalWriteSyncer()
219 if lerr != nil {
220 return lerr
221 }
222
223 lvl := zap.NewAtomicLevelAt(logutil.ConvertToZapLevel(cfg.LogLevel))
224 if cfg.Debug || cfg.LogLevel == "debug" {
225 // enable tracing even when "--debug --log-level info"
226 // in order to keep backward compatibility with <= v3.3
227 // TODO: remove "Debug" check in v3.5
228 grpc.EnableTracing = true
229 }
230
231 // WARN: do not change field names in encoder config
232 // journald logging writer assumes field names of "level" and "caller"
233 cr := zapcore.NewCore(
234 zapcore.NewJSONEncoder(logutil.DefaultZapLoggerConfig.EncoderConfig),
235 syncer,
236 lvl,
237 )
238 if cfg.ZapLoggerBuilder == nil {
239 cfg.ZapLoggerBuilder = func(c *Config) error {
240 c.logger = zap.New(cr, zap.AddCaller(), zap.ErrorOutput(syncer))
241 c.loggerMu.Lock()
242 defer c.loggerMu.Unlock()
243 c.loggerConfig = nil
244 c.loggerCore = cr
245 c.loggerWriteSyncer = syncer
246
247 grpcLogOnce.Do(func() {
248 grpclog.SetLoggerV2(logutil.NewGRPCLoggerV2FromZapCore(cr, syncer))
249 })
250 return nil
251 }
252 }
253 }
254
255 err := cfg.ZapLoggerBuilder(cfg)
256 if err != nil {
257 return err
258 }
259
260 logTLSHandshakeFailure := func(conn *tls.Conn, err error) {
261 state := conn.ConnectionState()
262 remoteAddr := conn.RemoteAddr().String()
263 serverName := state.ServerName
264 if len(state.PeerCertificates) > 0 {
265 cert := state.PeerCertificates[0]
266 ips := make([]string, len(cert.IPAddresses))
267 for i := range cert.IPAddresses {
268 ips[i] = cert.IPAddresses[i].String()
269 }
270 cfg.logger.Warn(
271 "rejected connection",
272 zap.String("remote-addr", remoteAddr),
273 zap.String("server-name", serverName),
274 zap.Strings("ip-addresses", ips),
275 zap.Strings("dns-names", cert.DNSNames),
276 zap.Error(err),
277 )
278 } else {
279 cfg.logger.Warn(
280 "rejected connection",
281 zap.String("remote-addr", remoteAddr),
282 zap.String("server-name", serverName),
283 zap.Error(err),
284 )
285 }
286 }
287 cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
288 cfg.PeerTLSInfo.HandshakeFailure = logTLSHandshakeFailure
289
290 default:
291 return fmt.Errorf("unknown logger option %q", cfg.Logger)
292 }
293
294 return nil
295}
296
297// NewZapCoreLoggerBuilder generates a zap core logger builder.
298func NewZapCoreLoggerBuilder(lg *zap.Logger, cr zapcore.Core, syncer zapcore.WriteSyncer) func(*Config) error {
299 return func(cfg *Config) error {
300 cfg.loggerMu.Lock()
301 defer cfg.loggerMu.Unlock()
302 cfg.logger = lg
303 cfg.loggerConfig = nil
304 cfg.loggerCore = cr
305 cfg.loggerWriteSyncer = syncer
306
307 grpcLogOnce.Do(func() {
308 grpclog.SetLoggerV2(logutil.NewGRPCLoggerV2FromZapCore(cr, syncer))
309 })
310 return nil
311 }
312}