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
| 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 | ) |