1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
44
45 func (cfg *Config) setupLogging() error {
46 switch cfg.Logger {
47 case "capnslog":
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
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
126 syncer, lerr := getJournalWriteSyncer()
127 if lerr != nil {
128 return lerr
129 }
130
131 lvl := zap.NewAtomicLevelAt(logutil.ConvertToZapLevel(cfg.LogLevel))
132
133
134
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
188
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
199
200 func NewZapCoreLoggerBuilder(lg *zap.Logger, _ zapcore.Core, _ zapcore.WriteSyncer) func(*Config) error {
201 return NewZapLoggerBuilder(lg)
202 }
203
204
205
206
207
208
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
227 func (logRotationConfig) Sync() error { return nil }
228
229
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
242 if len(logOutputs) == 1 && outputFilePaths == 0 {
243 return ErrLogRotationInvalidLogOutput
244 }
245
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