@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 10, 22:30 UTC

master
go 842 lines 25.6 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy 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 caddy
16
17 import (
18 "encoding/json"
19 "fmt"
20 "io"
21 "log"
22 "os"
23 "slices"
24 "strings"
25 "sync"
26 "time"
27
28 "go.uber.org/zap"
29 "go.uber.org/zap/zapcore"
30 "golang.org/x/term"
31
32 "github.com/caddyserver/caddy/v2/internal"
33 )
34
35 func init() {
36 RegisterModule(StdoutWriter{})
37 RegisterModule(StderrWriter{})
38 RegisterModule(DiscardWriter{})
39 }
40
41 // Logging facilitates logging within Caddy. The default log is
42 // called "default" and you can customize it. You can also define
43 // additional logs.
44 //
45 // By default, all logs at INFO level and higher are written to
46 // standard error ("stderr" writer) in a human-readable format
47 // ("console" encoder if stdout is an interactive terminal, "json"
48 // encoder otherwise).
49 //
50 // All defined logs accept all log entries by default, but you
51 // can filter by level and module/logger names. A logger's name
52 // is the same as the module's name, but a module may append to
53 // logger names for more specificity. For example, you can
54 // filter logs emitted only by HTTP handlers using the name
55 // "http.handlers", because all HTTP handler module names have
56 // that prefix.
57 //
58 // Caddy logs (except the sink) are zero-allocation, so they are
59 // very high-performing in terms of memory and CPU time. Enabling
60 // sampling can further increase throughput on extremely high-load
61 // servers.
62 type Logging struct {
63 // Sink is the destination for all unstructured logs emitted
64 // from Go's standard library logger. These logs are common
65 // in dependencies that are not designed specifically for use
66 // in Caddy. Because it is global and unstructured, the sink
67 // lacks most advanced features and customizations.
68 Sink *SinkLog `json:"sink,omitempty"`
69
70 // Logs are your logs, keyed by an arbitrary name of your
71 // choosing. The default log can be customized by defining
72 // a log called "default". You can further define other logs
73 // and filter what kinds of entries they accept.
74 Logs map[string]*CustomLog `json:"logs,omitempty"`
75
76 // a list of all keys for open writers; all writers
77 // that are opened to provision this logging config
78 // must have their keys added to this list so they
79 // can be closed when cleaning up
80 writerKeys []string
81 }
82
83 // openLogs sets up the config and opens all the configured writers.
84 // It closes its logs when ctx is canceled, so it should clean up
85 // after itself.
86 func (logging *Logging) openLogs(ctx Context) error {
87 // make sure to deallocate resources when context is done
88 ctx.OnCancel(func() {
89 err := logging.closeLogs()
90 if err != nil {
91 Log().Error("closing logs", zap.Error(err))
92 }
93 })
94
95 // set up the "sink" log first (std lib's default global logger)
96 if logging.Sink != nil {
97 err := logging.Sink.provision(ctx, logging)
98 if err != nil {
99 return fmt.Errorf("setting up sink log: %v", err)
100 }
101 }
102
103 // as a special case, set up the default structured Caddy log next
104 if err := logging.setupNewDefault(ctx); err != nil {
105 return err
106 }
107
108 // then set up any other custom logs
109 for name, l := range logging.Logs {
110 // the default log is already set up
111 if name == DefaultLoggerName {
112 continue
113 }
114
115 err := l.provision(ctx, logging)
116 if err != nil {
117 return fmt.Errorf("setting up custom log '%s': %v", name, err)
118 }
119
120 // Any other logs that use the discard writer can be deleted
121 // entirely. This avoids encoding and processing of each
122 // log entry that would just be thrown away anyway. Notably,
123 // we do not reach this point for the default log, which MUST
124 // exist, otherwise core log emissions would panic because
125 // they use the Log() function directly which expects a non-nil
126 // logger. Even if we keep logs with a discard writer, they
127 // have a nop core, and keeping them at all seems unnecessary.
128 if _, ok := l.writerOpener.(*DiscardWriter); ok {
129 delete(logging.Logs, name)
130 continue
131 }
132 }
133
134 return nil
135 }
136
137 func (logging *Logging) setupNewDefault(ctx Context) error {
138 if logging.Logs == nil {
139 logging.Logs = make(map[string]*CustomLog)
140 }
141
142 // extract the user-defined default log, if any
143 newDefault := new(defaultCustomLog)
144 if userDefault, ok := logging.Logs[DefaultLoggerName]; ok {
145 newDefault.CustomLog = userDefault
146 } else {
147 // if none, make one with our own default settings
148 var err error
149 newDefault, err = newDefaultProductionLog()
150 if err != nil {
151 return fmt.Errorf("setting up default Caddy log: %v", err)
152 }
153 logging.Logs[DefaultLoggerName] = newDefault.CustomLog
154 }
155
156 // options for the default logger
157 options, err := newDefault.CustomLog.buildOptions()
158 if err != nil {
159 return fmt.Errorf("setting up default log: %v", err)
160 }
161
162 // set up this new log
163 err = newDefault.CustomLog.provision(ctx, logging)
164 if err != nil {
165 return fmt.Errorf("setting up default log: %v", err)
166 }
167
168 filteringCore := &filteringCore{newDefault.CustomLog.core, newDefault.CustomLog}
169 newDefault.logger = zap.New(filteringCore, options...)
170
171 // redirect the default caddy logs
172 defaultLoggerMu.Lock()
173 oldDefault := defaultLogger
174 defaultLogger = newDefault
175 defaultLoggerMu.Unlock()
176
177 // if the new writer is different, indicate it in the logs for convenience
178 var newDefaultLogWriterKey, currentDefaultLogWriterKey string
179 var newDefaultLogWriterStr, currentDefaultLogWriterStr string
180 if newDefault.writerOpener != nil {
181 newDefaultLogWriterKey = newDefault.writerOpener.WriterKey()
182 newDefaultLogWriterStr = newDefault.writerOpener.String()
183 }
184 if oldDefault.writerOpener != nil {
185 currentDefaultLogWriterKey = oldDefault.writerOpener.WriterKey()
186 currentDefaultLogWriterStr = oldDefault.writerOpener.String()
187 }
188 if newDefaultLogWriterKey != currentDefaultLogWriterKey {
189 oldDefault.logger.Info("redirected default logger",
190 zap.String("from", currentDefaultLogWriterStr),
191 zap.String("to", newDefaultLogWriterStr),
192 )
193 }
194
195 // if we had a buffered core, flush its contents ASAP
196 // before we try to log anything else, so the order of
197 // logs is preserved
198 if oldBufferCore, ok := oldDefault.logger.Core().(*internal.LogBufferCore); ok {
199 oldBufferCore.FlushTo(newDefault.logger)
200 }
201
202 return nil
203 }
204
205 // closeLogs cleans up resources allocated during openLogs.
206 // A successful call to openLogs calls this automatically
207 // when the context is canceled.
208 func (logging *Logging) closeLogs() error {
209 for _, key := range logging.writerKeys {
210 _, err := writers.Delete(key)
211 if err != nil {
212 log.Printf("[ERROR] Closing log writer %v: %v", key, err)
213 }
214 }
215 return nil
216 }
217
218 // Logger returns a logger that is ready for the module to use.
219 func (logging *Logging) Logger(mod Module) *zap.Logger {
220 modID := string(mod.CaddyModule().ID)
221 var cores []zapcore.Core
222 var options []zap.Option
223
224 if logging != nil {
225 for _, l := range logging.Logs {
226 if l.matchesModule(modID) {
227 if len(l.Include) == 0 && len(l.Exclude) == 0 {
228 cores = append(cores, l.core)
229 continue
230 }
231 if len(options) == 0 {
232 newOptions, err := l.buildOptions()
233 if err != nil {
234 Log().Error("building options for logger", zap.String("module", modID), zap.Error(err))
235 }
236 options = newOptions
237 }
238 cores = append(cores, &filteringCore{Core: l.core, cl: l})
239 }
240 }
241 }
242
243 multiCore := zapcore.NewTee(cores...)
244
245 return zap.New(multiCore, options...).Named(modID)
246 }
247
248 // openWriter opens a writer using opener, and returns true if
249 // the writer is new, or false if the writer already exists.
250 func (logging *Logging) openWriter(opener WriterOpener) (io.WriteCloser, bool, error) {
251 key := opener.WriterKey()
252 writer, loaded, err := writers.LoadOrNew(key, func() (Destructor, error) {
253 w, err := opener.OpenWriter()
254 return writerDestructor{w}, err
255 })
256 if err != nil {
257 return nil, false, err
258 }
259 logging.writerKeys = append(logging.writerKeys, key)
260 return writer.(io.WriteCloser), !loaded, nil
261 }
262
263 // WriterOpener is a module that can open a log writer.
264 // It can return a human-readable string representation
265 // of itself so that operators can understand where
266 // the logs are going.
267 type WriterOpener interface {
268 fmt.Stringer
269
270 // WriterKey is a string that uniquely identifies this
271 // writer configuration. It is not shown to humans.
272 WriterKey() string
273
274 // OpenWriter opens a log for writing. The writer
275 // should be safe for concurrent use but need not
276 // be synchronous.
277 OpenWriter() (io.WriteCloser, error)
278 }
279
280 // IsWriterStandardStream returns true if the input is a
281 // writer-opener to a standard stream (stdout, stderr).
282 func IsWriterStandardStream(wo WriterOpener) bool {
283 switch wo.(type) {
284 case StdoutWriter, StderrWriter,
285 *StdoutWriter, *StderrWriter:
286 return true
287 }
288 return false
289 }
290
291 type writerDestructor struct {
292 io.WriteCloser
293 }
294
295 func (wdest writerDestructor) Destruct() error {
296 return wdest.Close()
297 }
298
299 // BaseLog contains the common logging parameters for logging.
300 type BaseLog struct {
301 // The module that writes out log entries for the sink.
302 WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"`
303
304 // The encoder is how the log entries are formatted or encoded.
305 EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
306
307 // Tees entries through a zap.Core module which can extract
308 // log entry metadata and fields for further processing.
309 CoreRaw json.RawMessage `json:"core,omitempty" caddy:"namespace=caddy.logging.cores inline_key=module"`
310
311 // Level is the minimum level to emit, and is inclusive.
312 // Possible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL
313 Level string `json:"level,omitempty"`
314
315 // Sampling configures log entry sampling. If enabled,
316 // only some log entries will be emitted. This is useful
317 // for improving performance on extremely high-pressure
318 // servers.
319 Sampling *LogSampling `json:"sampling,omitempty"`
320
321 // If true, the log entry will include the caller's
322 // file name and line number. Default off.
323 WithCaller bool `json:"with_caller,omitempty"`
324
325 // If non-zero, and `with_caller` is true, this many
326 // stack frames will be skipped when determining the
327 // caller. Default 0.
328 WithCallerSkip int `json:"with_caller_skip,omitempty"`
329
330 // If not empty, the log entry will include a stack trace
331 // for all logs at the given level or higher. See `level`
332 // for possible values. Default off.
333 WithStacktrace string `json:"with_stacktrace,omitempty"`
334
335 writerOpener WriterOpener
336 writer io.WriteCloser
337 encoder zapcore.Encoder
338 levelEnabler zapcore.LevelEnabler
339 core zapcore.Core
340 }
341
342 func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
343 if cl.WriterRaw != nil {
344 mod, err := ctx.LoadModule(cl, "WriterRaw")
345 if err != nil {
346 return fmt.Errorf("loading log writer module: %v", err)
347 }
348 cl.writerOpener = mod.(WriterOpener)
349 }
350 if cl.writerOpener == nil {
351 cl.writerOpener = StderrWriter{}
352 }
353 var err error
354 cl.writer, _, err = logging.openWriter(cl.writerOpener)
355 if err != nil {
356 return fmt.Errorf("opening log writer using %#v: %v", cl.writerOpener, err)
357 }
358
359 // set up the log level
360 cl.levelEnabler, err = parseLevel(cl.Level)
361 if err != nil {
362 return err
363 }
364
365 if cl.EncoderRaw != nil {
366 mod, err := ctx.LoadModule(cl, "EncoderRaw")
367 if err != nil {
368 return fmt.Errorf("loading log encoder module: %v", err)
369 }
370 cl.encoder = mod.(zapcore.Encoder)
371
372 // if the encoder module needs the writer to determine
373 // the correct default to use for a nested encoder, we
374 // pass it down as a secondary provisioning step
375 if cfd, ok := mod.(ConfiguresFormatterDefault); ok {
376 if err := cfd.ConfigureDefaultFormat(cl.writerOpener); err != nil {
377 return fmt.Errorf("configuring default format for encoder module: %v", err)
378 }
379 }
380 }
381 if cl.encoder == nil {
382 cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener)
383 }
384 cl.buildCore()
385 if cl.CoreRaw != nil {
386 mod, err := ctx.LoadModule(cl, "CoreRaw")
387 if err != nil {
388 return fmt.Errorf("loading log core module: %v", err)
389 }
390 core := mod.(zapcore.Core)
391 cl.core = zapcore.NewTee(cl.core, core)
392 }
393 return nil
394 }
395
396 func (cl *BaseLog) buildCore() {
397 // logs which only discard their output don't need
398 // to perform encoding or any other processing steps
399 // at all, so just shortcut to a nop core instead
400 if _, ok := cl.writerOpener.(*DiscardWriter); ok {
401 cl.core = zapcore.NewNopCore()
402 return
403 }
404 c := zapcore.NewCore(
405 cl.encoder,
406 zapcore.AddSync(cl.writer),
407 cl.levelEnabler,
408 )
409 if cl.Sampling != nil {
410 if cl.Sampling.Interval == 0 {
411 cl.Sampling.Interval = 1 * time.Second
412 }
413 if cl.Sampling.First == 0 {
414 cl.Sampling.First = 100
415 }
416 if cl.Sampling.Thereafter == 0 {
417 cl.Sampling.Thereafter = 100
418 }
419 c = zapcore.NewSamplerWithOptions(c, cl.Sampling.Interval,
420 cl.Sampling.First, cl.Sampling.Thereafter)
421 }
422 cl.core = c
423 }
424
425 func (cl *BaseLog) buildOptions() ([]zap.Option, error) {
426 var options []zap.Option
427 if cl.WithCaller {
428 options = append(options, zap.AddCaller())
429 if cl.WithCallerSkip != 0 {
430 options = append(options, zap.AddCallerSkip(cl.WithCallerSkip))
431 }
432 }
433 if cl.WithStacktrace != "" {
434 levelEnabler, err := parseLevel(cl.WithStacktrace)
435 if err != nil {
436 return options, fmt.Errorf("setting up default Caddy log: %v", err)
437 }
438 options = append(options, zap.AddStacktrace(levelEnabler))
439 }
440 return options, nil
441 }
442
443 // SinkLog configures the default Go standard library
444 // global logger in the log package. This is necessary because
445 // module dependencies which are not built specifically for
446 // Caddy will use the standard logger. This is also known as
447 // the "sink" logger.
448 type SinkLog struct {
449 BaseLog
450 }
451
452 func (sll *SinkLog) provision(ctx Context, logging *Logging) error {
453 if err := sll.provisionCommon(ctx, logging); err != nil {
454 return err
455 }
456
457 options, err := sll.buildOptions()
458 if err != nil {
459 return err
460 }
461
462 logger := zap.New(sll.core, options...)
463 ctx.cleanupFuncs = append(ctx.cleanupFuncs, zap.RedirectStdLog(logger))
464 return nil
465 }
466
467 // CustomLog represents a custom logger configuration.
468 //
469 // By default, a log will emit all log entries. Some entries
470 // will be skipped if sampling is enabled. Further, the Include
471 // and Exclude parameters define which loggers (by name) are
472 // allowed or rejected from emitting in this log. If both Include
473 // and Exclude are populated, their values must be mutually
474 // exclusive, and longer namespaces have priority. If neither
475 // are populated, all logs are emitted.
476 type CustomLog struct {
477 BaseLog
478
479 // Include defines the names of loggers to emit in this
480 // log. For example, to include only logs emitted by the
481 // admin API, you would include "admin.api".
482 Include []string `json:"include,omitempty"`
483
484 // Exclude defines the names of loggers that should be
485 // skipped by this log. For example, to exclude only
486 // HTTP access logs, you would exclude "http.log.access".
487 Exclude []string `json:"exclude,omitempty"`
488 }
489
490 func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
491 if err := cl.provisionCommon(ctx, logging); err != nil {
492 return err
493 }
494
495 // If both Include and Exclude lists are populated, then each item must
496 // be a superspace or subspace of an item in the other list, because
497 // populating both lists means that any given item is either a rule
498 // or an exception to another rule. But if the item is not a super-
499 // or sub-space of any item in the other list, it is neither a rule
500 // nor an exception, and is a contradiction. Ensure, too, that the
501 // sets do not intersect, which is also a contradiction.
502 if len(cl.Include) > 0 && len(cl.Exclude) > 0 {
503 // prevent intersections
504 for _, allow := range cl.Include {
505 if slices.Contains(cl.Exclude, allow) {
506 return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow)
507 }
508 }
509
510 // ensure namespaces are nested
511 outer:
512 for _, allow := range cl.Include {
513 for _, deny := range cl.Exclude {
514 if strings.HasPrefix(allow+".", deny+".") ||
515 strings.HasPrefix(deny+".", allow+".") {
516 continue outer
517 }
518 }
519 return fmt.Errorf("when both include and exclude are populated, each element must be a superspace or subspace of one in the other list; check '%s' in include", allow)
520 }
521 }
522 return nil
523 }
524
525 func (cl *CustomLog) matchesModule(moduleID string) bool {
526 return cl.loggerAllowed(moduleID, true)
527 }
528
529 // loggerAllowed returns true if name is allowed to emit
530 // to cl. isModule should be true if name is the name of
531 // a module and you want to see if ANY of that module's
532 // logs would be permitted.
533 func (cl *CustomLog) loggerAllowed(name string, isModule bool) bool {
534 // accept all loggers by default
535 if len(cl.Include) == 0 && len(cl.Exclude) == 0 {
536 return true
537 }
538
539 // append a dot so that partial names don't match
540 // (i.e. we don't want "foo.b" to match "foo.bar"); we
541 // will also have to append a dot when we do HasPrefix
542 // below to compensate for when namespaces are equal
543 if name != "" && name != "*" && name != "." {
544 name += "."
545 }
546
547 var longestAccept, longestReject int
548
549 if len(cl.Include) > 0 {
550 for _, namespace := range cl.Include {
551 var hasPrefix bool
552 if isModule {
553 // the namespace is the module or one of its
554 // sub-loggers (e.g. "http.log.access" for "http")
555 hasPrefix = strings.HasPrefix(namespace+".", name) ||
556 // the namespace is a parent of the module (e.g.
557 // "http.handlers" for "http.handlers.file_server")
558 strings.HasPrefix(name, namespace+".")
559 } else {
560 hasPrefix = strings.HasPrefix(name, namespace+".")
561 }
562 if hasPrefix && len(namespace) > longestAccept {
563 longestAccept = len(namespace)
564 }
565 }
566 // the include list was populated, meaning that
567 // a match in this list is absolutely required
568 // if we are to accept the entry
569 if longestAccept == 0 {
570 return false
571 }
572 }
573
574 if len(cl.Exclude) > 0 {
575 for _, namespace := range cl.Exclude {
576 // * == all logs emitted by modules
577 // . == all logs emitted by core
578 if (namespace == "*" && name != ".") ||
579 (namespace == "." && name == ".") {
580 return false
581 }
582 if strings.HasPrefix(name, namespace+".") &&
583 len(namespace) > longestReject {
584 longestReject = len(namespace)
585 }
586 }
587 // the reject list is populated, so we have to
588 // reject this entry if its match is better
589 // than the best from the accept list
590 if longestReject > longestAccept {
591 return false
592 }
593 }
594
595 return (longestAccept > longestReject) ||
596 (len(cl.Include) == 0 && longestReject == 0)
597 }
598
599 // filteringCore filters log entries based on logger name,
600 // according to the rules of a CustomLog.
601 type filteringCore struct {
602 zapcore.Core
603 cl *CustomLog
604 }
605
606 // With properly wraps With.
607 func (fc *filteringCore) With(fields []zapcore.Field) zapcore.Core {
608 return &filteringCore{
609 Core: fc.Core.With(fields),
610 cl: fc.cl,
611 }
612 }
613
614 // Check only allows the log entry if its logger name
615 // is allowed from the include/exclude rules of fc.cl.
616 func (fc *filteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
617 if fc.cl.loggerAllowed(e.LoggerName, false) {
618 return fc.Core.Check(e, ce)
619 }
620 return ce
621 }
622
623 // LogSampling configures log entry sampling.
624 type LogSampling struct {
625 // The window over which to conduct sampling.
626 Interval time.Duration `json:"interval,omitempty"`
627
628 // Log this many entries within a given level and
629 // message for each interval.
630 First int `json:"first,omitempty"`
631
632 // If more entries with the same level and message
633 // are seen during the same interval, keep one in
634 // this many entries until the end of the interval.
635 Thereafter int `json:"thereafter,omitempty"`
636 }
637
638 type (
639 // StdoutWriter writes logs to standard out.
640 StdoutWriter struct{}
641
642 // StderrWriter writes logs to standard error.
643 StderrWriter struct{}
644
645 // DiscardWriter discards all writes.
646 DiscardWriter struct{}
647 )
648
649 // CaddyModule returns the Caddy module information.
650 func (StdoutWriter) CaddyModule() ModuleInfo {
651 return ModuleInfo{
652 ID: "caddy.logging.writers.stdout",
653 New: func() Module { return new(StdoutWriter) },
654 }
655 }
656
657 // CaddyModule returns the Caddy module information.
658 func (StderrWriter) CaddyModule() ModuleInfo {
659 return ModuleInfo{
660 ID: "caddy.logging.writers.stderr",
661 New: func() Module { return new(StderrWriter) },
662 }
663 }
664
665 // CaddyModule returns the Caddy module information.
666 func (DiscardWriter) CaddyModule() ModuleInfo {
667 return ModuleInfo{
668 ID: "caddy.logging.writers.discard",
669 New: func() Module { return new(DiscardWriter) },
670 }
671 }
672
673 func (StdoutWriter) String() string { return "stdout" }
674 func (StderrWriter) String() string { return "stderr" }
675 func (DiscardWriter) String() string { return "discard" }
676
677 // WriterKey returns a unique key representing stdout.
678 func (StdoutWriter) WriterKey() string { return "std:out" }
679
680 // WriterKey returns a unique key representing stderr.
681 func (StderrWriter) WriterKey() string { return "std:err" }
682
683 // WriterKey returns a unique key representing discard.
684 func (DiscardWriter) WriterKey() string { return "discard" }
685
686 // OpenWriter returns os.Stdout that can't be closed.
687 func (StdoutWriter) OpenWriter() (io.WriteCloser, error) {
688 return notClosable{os.Stdout}, nil
689 }
690
691 // OpenWriter returns os.Stderr that can't be closed.
692 func (StderrWriter) OpenWriter() (io.WriteCloser, error) {
693 return notClosable{os.Stderr}, nil
694 }
695
696 // OpenWriter returns io.Discard that can't be closed.
697 func (DiscardWriter) OpenWriter() (io.WriteCloser, error) {
698 return notClosable{io.Discard}, nil
699 }
700
701 // notClosable is an io.WriteCloser that can't be closed.
702 type notClosable struct{ io.Writer }
703
704 func (fc notClosable) Close() error { return nil }
705
706 type defaultCustomLog struct {
707 *CustomLog
708 logger *zap.Logger
709 }
710
711 // newDefaultProductionLog configures a custom log that is
712 // intended for use by default if no other log is specified
713 // in a config. It writes to stderr, uses the console encoder,
714 // and enables INFO-level logs and higher.
715 func newDefaultProductionLog() (*defaultCustomLog, error) {
716 cl := new(CustomLog)
717 cl.writerOpener = StderrWriter{}
718 var err error
719 cl.writer, err = cl.writerOpener.OpenWriter()
720 if err != nil {
721 return nil, err
722 }
723 cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener)
724 cl.levelEnabler = zapcore.InfoLevel
725
726 cl.buildCore()
727
728 logger := zap.New(cl.core)
729
730 // capture logs from other libraries which
731 // may not be using zap logging directly
732 _ = zap.RedirectStdLog(logger)
733
734 return &defaultCustomLog{
735 CustomLog: cl,
736 logger: logger,
737 }, nil
738 }
739
740 func newDefaultProductionLogEncoder(wo WriterOpener) zapcore.Encoder {
741 encCfg := zap.NewProductionEncoderConfig()
742 if IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) {
743 // if interactive terminal, make output more human-readable by default
744 encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
745 encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000"))
746 }
747 if coloringEnabled {
748 encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
749 }
750
751 return zapcore.NewConsoleEncoder(encCfg)
752 }
753 return zapcore.NewJSONEncoder(encCfg)
754 }
755
756 func parseLevel(levelInput string) (zapcore.LevelEnabler, error) {
757 repl := NewReplacer()
758 level, err := repl.ReplaceOrErr(levelInput, true, true)
759 if err != nil {
760 return nil, fmt.Errorf("invalid log level: %v", err)
761 }
762 level = strings.ToLower(level)
763
764 // set up the log level
765 switch level {
766 case "debug":
767 return zapcore.DebugLevel, nil
768 case "", "info":
769 return zapcore.InfoLevel, nil
770 case "warn":
771 return zapcore.WarnLevel, nil
772 case "error":
773 return zapcore.ErrorLevel, nil
774 case "panic":
775 return zapcore.PanicLevel, nil
776 case "fatal":
777 return zapcore.FatalLevel, nil
778 default:
779 return nil, fmt.Errorf("unrecognized log level: %s", level)
780 }
781 }
782
783 // Log returns the current default logger.
784 func Log() *zap.Logger {
785 defaultLoggerMu.RLock()
786 defer defaultLoggerMu.RUnlock()
787 return defaultLogger.logger
788 }
789
790 // BufferedLog sets the default logger to one that buffers
791 // logs before a config is loaded.
792 // Returns the buffered logger, the original default logger
793 // (for flushing on errors), and the buffer core so that the
794 // caller can flush the logs after the config is loaded or
795 // fails to load.
796 func BufferedLog() (*zap.Logger, *zap.Logger, *internal.LogBufferCore) {
797 defaultLoggerMu.Lock()
798 defer defaultLoggerMu.Unlock()
799 origLogger := defaultLogger.logger
800 bufferCore := internal.NewLogBufferCore(zap.InfoLevel)
801 defaultLogger.logger = zap.New(bufferCore)
802 return defaultLogger.logger, origLogger, bufferCore
803 }
804
805 // UnbufferDefaultLogger restores the default logger's underlying zap.Logger to orig
806 // if the default logger is still pointing to a buffer core, flushing any remaining buffered logs.
807 func UnbufferDefaultLogger(orig *zap.Logger) {
808 defaultLoggerMu.Lock()
809 defer defaultLoggerMu.Unlock()
810 if defaultLogger != nil && defaultLogger.logger != nil {
811 if bufferCore, ok := defaultLogger.logger.Core().(*internal.LogBufferCore); ok {
812 bufferCore.FlushTo(orig)
813 defaultLogger.logger = orig
814 }
815 }
816 }
817
818 var (
819 coloringEnabled = os.Getenv("NO_COLOR") == "" && os.Getenv("TERM") != "xterm-mono"
820 defaultLogger, _ = newDefaultProductionLog()
821 defaultLoggerMu sync.RWMutex
822 )
823
824 var writers = NewUsagePool()
825
826 // ConfiguresFormatterDefault is an optional interface that
827 // encoder modules can implement to configure the default
828 // format of their encoder. This is useful for encoders
829 // which nest an encoder, that needs to know the writer
830 // in order to determine the correct default.
831 type ConfiguresFormatterDefault interface {
832 ConfigureDefaultFormat(WriterOpener) error
833 }
834
835 const DefaultLoggerName = "default"
836
837 // Interface guards
838 var (
839 _ io.WriteCloser = (*notClosable)(nil)
840 _ WriterOpener = (*StdoutWriter)(nil)
841 _ WriterOpener = (*StderrWriter)(nil)
842 )