diff options
Diffstat (limited to 'common/loggers/logger.go')
-rw-r--r-- | common/loggers/logger.go | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/common/loggers/logger.go b/common/loggers/logger.go new file mode 100644 index 000000000..f851513b3 --- /dev/null +++ b/common/loggers/logger.go @@ -0,0 +1,379 @@ +// Copyright 2024 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 + DistinctLevel logg.Level + StoreErrors bool + HandlerPost func(e *logg.Entry) error + SuppressStatements 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{ + logCounters, + } + + if opts.Level == logg.LevelTrace { + // Trace is used during development only, and it's useful to + // only see the trace messages. + handlers = append(handlers, + logg.HandlerFunc(func(e *logg.Entry) error { + if e.Level != logg.LevelTrace { + return logg.ErrStopLogEntry + } + return nil + }), + ) + } + + handlers = append(handlers, whiteSpaceTrimmer(), logHandler) + + 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.DistinctLevel != 0 { + logOnce = newLogOnceHandler(opts.DistinctLevel) + logHandler = newStopHandler(logOnce, logHandler) + } + + if opts.SuppressStatements != nil && len(opts.SuppressStatements) > 0 { + logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppressStatements), 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, + tracel: l.WithLevel(logg.LevelTrace), + 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{ + DistinctLevel: logg.LevelWarn, + Level: logg.LevelWarn, + Stdout: os.Stdout, + Stderr: os.Stdout, + } + return New(opts) +} + +func NewTrace() Logger { + opts := Options{ + DistinctLevel: logg.LevelWarn, + Level: logg.LevelTrace, + Stdout: os.Stdout, + Stderr: os.Stdout, + } + return New(opts) +} + +func LevelLoggerToWriter(l logg.LevelLogger) io.Writer { + return logWriter{l: l} +} + +type Logger interface { + Debug() logg.LevelLogger + Debugf(format string, v ...any) + Debugln(v ...any) + Error() logg.LevelLogger + Errorf(format string, v ...any) + Erroridf(id, format string, v ...any) + Errorln(v ...any) + Errors() string + 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) + Warnidf(id, format string, v ...any) + Warnln(v ...any) + Deprecatef(fail bool, format string, v ...any) + Trace(s logg.StringFunc) +} + +type logAdapter struct { + logCounters *logLevelCounter + errors *strings.Builder + reset func() + out io.Writer + level logg.Level + logger logg.Logger + tracel logg.LevelLogger + debugl logg.LevelLogger + infol logg.LevelLogger + warnl logg.LevelLogger + errorl logg.LevelLogger +} + +func (l *logAdapter) Debug() logg.LevelLogger { + return l.debugl +} + +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) Erroridf(id, format string, v ...any) { + format += l.idfInfoStatement("error", id, format) + l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...) +} + +func (l *logAdapter) Warnidf(id, format string, v ...any) { + format += l.idfInfoStatement("warning", id, format) + l.warnl.WithField(FieldNameStatementID, id).Logf(format, v...) +} + +func (l *logAdapter) idfInfoStatement(what, id, format string) string { + return fmt.Sprintf("\nYou can suppress this %s by adding the following to your site configuration:\nignoreLogs = ['%s']", what, id) +} + +func (l *logAdapter) Trace(s logg.StringFunc) { + l.tracel.Log(s) +} + +func (l *logAdapter) sprint(v ...any) string { + return strings.TrimRight(fmt.Sprintln(v...), "\n") +} + +func (l *logAdapter) Deprecatef(fail bool, format string, v ...any) { + format = "DEPRECATED: " + format + if fail { + l.errorl.Logf(format, v...) + } else { + l.warnl.Logf(format, v...) + } +} + +type logWriter struct { + l logg.LevelLogger +} + +func (w logWriter) Write(p []byte) (n int, err error) { + w.l.Log(logg.String(string(p))) + return len(p), nil +} + +func TimeTrackf(l logg.LevelLogger, start time.Time, fields logg.Fields, format string, a ...any) { + elapsed := time.Since(start) + if fields != nil { + l = l.WithFields(fields) + } + l.WithField("duration", elapsed).Logf(format, a...) +} + +func TimeTrackfn(fn func() (logg.LevelLogger, error)) error { + start := time.Now() + l, err := fn() + elapsed := time.Since(start) + l.WithField("duration", elapsed).Logf("") + return err +} |