// Copyright 2023 The Hugo Authors. All rights reserved. // Some functions in this file (see comments) is based on the Go source code, // copyright The Go Authors and governed by a BSD-style license. // // 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 loggers import ( "fmt" "io" "os" "strings" "time" "github.com/bep/logg" "github.com/bep/logg/handlers/multi" "github.com/gohugoio/hugo/common/terminal" ) var ( reservedFieldNamePrefix = "__h_field_" // FieldNameCmd is the name of the field that holds the command name. FieldNameCmd = reservedFieldNamePrefix + "_cmd" // Used to suppress statements. FieldNameStatementID = reservedFieldNamePrefix + "__h_field_statement_id" ) // Options defines options for the logger. type Options struct { Level logg.Level Stdout io.Writer Stderr io.Writer Distinct bool StoreErrors bool HandlerPost func(e *logg.Entry) error SuppresssStatements map[string]bool } // New creates a new logger with the given options. func New(opts Options) Logger { if opts.Stdout == nil { opts.Stdout = os.Stdout } if opts.Stderr == nil { opts.Stderr = os.Stdout } if opts.Level == 0 { opts.Level = logg.LevelWarn } var logHandler logg.Handler if terminal.PrintANSIColors(os.Stdout) { logHandler = newDefaultHandler(opts.Stdout, opts.Stderr) } else { logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil) } errorsw := &strings.Builder{} logCounters := newLogLevelCounter() handlers := []logg.Handler{ whiteSpaceTrimmer(), logHandler, logCounters, } if opts.HandlerPost != nil { var hookHandler logg.HandlerFunc = func(e *logg.Entry) error { opts.HandlerPost(e) return nil } handlers = append(handlers, hookHandler) } if opts.StoreErrors { h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool { return e.Level >= logg.LevelError }) handlers = append(handlers, h) } logHandler = multi.New(handlers...) var logOnce *logOnceHandler if opts.Distinct { logOnce = newLogOnceHandler(logg.LevelWarn) logHandler = newStopHandler(logOnce, logHandler) } if opts.SuppresssStatements != nil && len(opts.SuppresssStatements) > 0 { logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppresssStatements), logHandler) } logger := logg.New( logg.Options{ Level: opts.Level, Handler: logHandler, }, ) l := logger.WithLevel(opts.Level) reset := func() { logCounters.mu.Lock() defer logCounters.mu.Unlock() logCounters.counters = make(map[logg.Level]int) errorsw.Reset() if logOnce != nil { logOnce.reset() } } return &logAdapter{ logCounters: logCounters, errors: errorsw, reset: reset, out: opts.Stdout, level: opts.Level, logger: logger, debugl: l.WithLevel(logg.LevelDebug), infol: l.WithLevel(logg.LevelInfo), warnl: l.WithLevel(logg.LevelWarn), errorl: l.WithLevel(logg.LevelError), } } // NewDefault creates a new logger with the default options. func NewDefault() Logger { opts := Options{ Distinct: true, Level: logg.LevelWarn, Stdout: os.Stdout, Stderr: os.Stdout, } return New(opts) } func LevelLoggerToWriter(l logg.LevelLogger) io.Writer { return logWriter{l: l} } type Logger interface { Debugf(format string, v ...any) Debugln(v ...any) Error() logg.LevelLogger Errorf(format string, v ...any) Errorln(v ...any) Errors() string Errorsf(id, format string, v ...any) Info() logg.LevelLogger InfoCommand(command string) logg.LevelLogger Infof(format string, v ...any) Infoln(v ...any) Level() logg.Level LoggCount(logg.Level) int Logger() logg.Logger Out() io.Writer Printf(format string, v ...any) Println(v ...any) PrintTimerIfDelayed(start time.Time, name string) Reset() Warn() logg.LevelLogger WarnCommand(command string) logg.LevelLogger Warnf(format string, v ...any) Warnln(v ...any) } type logAdapter struct { logCounters *logLevelCounter errors *strings.Builder reset func() out io.Writer level logg.Level logger logg.Logger debugl logg.LevelLogger infol logg.LevelLogger warnl logg.LevelLogger errorl logg.LevelLogger } func (l *logAdapter) Debugf(format string, v ...any) { l.debugl.Logf(format, v...) } func (l *logAdapter) Debugln(v ...any) { l.debugl.Logf(l.sprint(v...)) } func (l *logAdapter) Info() logg.LevelLogger { return l.infol } func (l *logAdapter) InfoCommand(command string) logg.LevelLogger { return l.infol.WithField(FieldNameCmd, command) } func (l *logAdapter) Infof(format string, v ...any) { l.infol.Logf(format, v...) } func (l *logAdapter) Infoln(v ...any) { l.infol.Logf(l.sprint(v...)) } func (l *logAdapter) Level() logg.Level { return l.level } func (l *logAdapter) LoggCount(level logg.Level) int { l.logCounters.mu.RLock() defer l.logCounters.mu.RUnlock() return l.logCounters.counters[level] } func (l *logAdapter) Logger() logg.Logger { return l.logger } func (l *logAdapter) Out() io.Writer { return l.out } // PrintTimerIfDelayed prints a time statement to the FEEDBACK logger // if considerable time is spent. func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) { elapsed := time.Since(start) milli := int(1000 * elapsed.Seconds()) if milli < 500 { return } l.Printf("%s in %v ms", name, milli) } func (l *logAdapter) Printf(format string, v ...any) { // Add trailing newline if not present. if !strings.HasSuffix(format, "\n") { format += "\n" } fmt.Fprintf(l.out, format, v...) } func (l *logAdapter) Println(v ...any) { fmt.Fprintln(l.out, v...) } func (l *logAdapter) Reset() { l.reset() } func (l *logAdapter) Warn() logg.LevelLogger { return l.warnl } func (l *logAdapter) Warnf(format string, v ...any) { l.warnl.Logf(format, v...) } func (l *logAdapter) WarnCommand(command string) logg.LevelLogger { return l.warnl.WithField(FieldNameCmd, command) } func (l *logAdapter) Warnln(v ...any) { l.warnl.Logf(l.sprint(v...)) } func (l *logAdapter) Error() logg.LevelLogger { return l.errorl } func (l *logAdapter) Errorf(format string, v ...any) { l.errorl.Logf(format, v...) } func (l *logAdapter) Errorln(v ...any) { l.errorl.Logf(l.sprint(v...)) } func (l *logAdapter) Errors() string { return l.errors.String() } func (l *logAdapter) Errorsf(id, format string, v ...any) { l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...) } func (l *logAdapter) sprint(v ...any) string { return strings.TrimRight(fmt.Sprintln(v...), "\n") } type logWriter struct { l logg.LevelLogger } func (w logWriter) Write(p []byte) (n int, err error) { w.l.Logf("%s", p) return len(p), nil }