summaryrefslogtreecommitdiffstats
path: root/common/loggers/logger.go
diff options
context:
space:
mode:
Diffstat (limited to 'common/loggers/logger.go')
-rw-r--r--common/loggers/logger.go379
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
+}