github.com/neohugo/neohugo@v0.123.8/common/loggers/logger.go (about)

     1  // Copyright 2024 The Hugo Authors. All rights reserved.
     2  // Some functions in this file (see comments) is based on the Go source code,
     3  // copyright The Go Authors and  governed by a BSD-style license.
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  // http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package loggers
    17  
    18  import (
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/bep/logg"
    26  	"github.com/bep/logg/handlers/multi"
    27  	"github.com/neohugo/neohugo/common/terminal"
    28  )
    29  
    30  var (
    31  	reservedFieldNamePrefix = "__h_field_"
    32  	// FieldNameCmd is the name of the field that holds the command name.
    33  	FieldNameCmd = reservedFieldNamePrefix + "_cmd"
    34  	// Used to suppress statements.
    35  	FieldNameStatementID = reservedFieldNamePrefix + "__h_field_statement_id"
    36  )
    37  
    38  // Options defines options for the logger.
    39  type Options struct {
    40  	Level              logg.Level
    41  	Stdout             io.Writer
    42  	Stderr             io.Writer
    43  	DistinctLevel      logg.Level
    44  	StoreErrors        bool
    45  	HandlerPost        func(e *logg.Entry) error
    46  	SuppressStatements map[string]bool
    47  }
    48  
    49  // New creates a new logger with the given options.
    50  func New(opts Options) Logger {
    51  	if opts.Stdout == nil {
    52  		opts.Stdout = os.Stdout
    53  	}
    54  	if opts.Stderr == nil {
    55  		opts.Stderr = os.Stdout
    56  	}
    57  	if opts.Level == 0 {
    58  		opts.Level = logg.LevelWarn
    59  	}
    60  
    61  	var logHandler logg.Handler
    62  	if terminal.PrintANSIColors(os.Stdout) {
    63  		logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
    64  	} else {
    65  		logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil)
    66  	}
    67  
    68  	errorsw := &strings.Builder{}
    69  	logCounters := newLogLevelCounter()
    70  	handlers := []logg.Handler{
    71  		logCounters,
    72  	}
    73  
    74  	if opts.Level == logg.LevelTrace {
    75  		// Trace is used during development only, and it's useful to
    76  		// only see the trace messages.
    77  		handlers = append(handlers,
    78  			logg.HandlerFunc(func(e *logg.Entry) error {
    79  				if e.Level != logg.LevelTrace {
    80  					return logg.ErrStopLogEntry
    81  				}
    82  				return nil
    83  			}),
    84  		)
    85  	}
    86  
    87  	handlers = append(handlers, whiteSpaceTrimmer(), logHandler)
    88  
    89  	if opts.HandlerPost != nil {
    90  		var hookHandler logg.HandlerFunc = func(e *logg.Entry) error {
    91  			opts.HandlerPost(e) // nolint
    92  			return nil
    93  		}
    94  		handlers = append(handlers, hookHandler)
    95  	}
    96  
    97  	if opts.StoreErrors {
    98  		h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
    99  			return e.Level >= logg.LevelError
   100  		})
   101  
   102  		handlers = append(handlers, h)
   103  	}
   104  
   105  	logHandler = multi.New(handlers...)
   106  
   107  	var logOnce *logOnceHandler
   108  	if opts.DistinctLevel != 0 {
   109  		logOnce = newLogOnceHandler(opts.DistinctLevel)
   110  		logHandler = newStopHandler(logOnce, logHandler)
   111  	}
   112  
   113  	if opts.SuppressStatements != nil && len(opts.SuppressStatements) > 0 {
   114  		logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppressStatements), logHandler)
   115  	}
   116  
   117  	logger := logg.New(
   118  		logg.Options{
   119  			Level:   opts.Level,
   120  			Handler: logHandler,
   121  		},
   122  	)
   123  
   124  	l := logger.WithLevel(opts.Level)
   125  
   126  	reset := func() {
   127  		logCounters.mu.Lock()
   128  		defer logCounters.mu.Unlock()
   129  		logCounters.counters = make(map[logg.Level]int)
   130  		errorsw.Reset()
   131  		if logOnce != nil {
   132  			logOnce.reset()
   133  		}
   134  	}
   135  
   136  	return &logAdapter{
   137  		logCounters: logCounters,
   138  		errors:      errorsw,
   139  		reset:       reset,
   140  		out:         opts.Stdout,
   141  		level:       opts.Level,
   142  		logger:      logger,
   143  		tracel:      l.WithLevel(logg.LevelTrace),
   144  		debugl:      l.WithLevel(logg.LevelDebug),
   145  		infol:       l.WithLevel(logg.LevelInfo),
   146  		warnl:       l.WithLevel(logg.LevelWarn),
   147  		errorl:      l.WithLevel(logg.LevelError),
   148  	}
   149  }
   150  
   151  // NewDefault creates a new logger with the default options.
   152  func NewDefault() Logger {
   153  	opts := Options{
   154  		DistinctLevel: logg.LevelWarn,
   155  		Level:         logg.LevelWarn,
   156  		Stdout:        os.Stdout,
   157  		Stderr:        os.Stdout,
   158  	}
   159  	return New(opts)
   160  }
   161  
   162  func NewTrace() Logger {
   163  	opts := Options{
   164  		DistinctLevel: logg.LevelWarn,
   165  		Level:         logg.LevelTrace,
   166  		Stdout:        os.Stdout,
   167  		Stderr:        os.Stdout,
   168  	}
   169  	return New(opts)
   170  }
   171  
   172  func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
   173  	return logWriter{l: l}
   174  }
   175  
   176  type Logger interface {
   177  	Debug() logg.LevelLogger
   178  	Debugf(format string, v ...any)
   179  	Debugln(v ...any)
   180  	Error() logg.LevelLogger
   181  	Errorf(format string, v ...any)
   182  	Erroridf(id, format string, v ...any)
   183  	Errorln(v ...any)
   184  	Errors() string
   185  	Info() logg.LevelLogger
   186  	InfoCommand(command string) logg.LevelLogger
   187  	Infof(format string, v ...any)
   188  	Infoln(v ...any)
   189  	Level() logg.Level
   190  	LoggCount(logg.Level) int
   191  	Logger() logg.Logger
   192  	Out() io.Writer
   193  	Printf(format string, v ...any)
   194  	Println(v ...any)
   195  	PrintTimerIfDelayed(start time.Time, name string)
   196  	Reset()
   197  	Warn() logg.LevelLogger
   198  	WarnCommand(command string) logg.LevelLogger
   199  	Warnf(format string, v ...any)
   200  	Warnidf(id, format string, v ...any)
   201  	Warnln(v ...any)
   202  	Deprecatef(fail bool, format string, v ...any)
   203  	Trace(s logg.StringFunc)
   204  }
   205  
   206  type logAdapter struct {
   207  	logCounters *logLevelCounter
   208  	errors      *strings.Builder
   209  	reset       func()
   210  	out         io.Writer
   211  	level       logg.Level
   212  	logger      logg.Logger
   213  	tracel      logg.LevelLogger
   214  	debugl      logg.LevelLogger
   215  	infol       logg.LevelLogger
   216  	warnl       logg.LevelLogger
   217  	errorl      logg.LevelLogger
   218  }
   219  
   220  func (l *logAdapter) Debug() logg.LevelLogger {
   221  	return l.debugl
   222  }
   223  
   224  func (l *logAdapter) Debugf(format string, v ...any) {
   225  	l.debugl.Logf(format, v...)
   226  }
   227  
   228  func (l *logAdapter) Debugln(v ...any) {
   229  	l.debugl.Logf(l.sprint(v...))
   230  }
   231  
   232  func (l *logAdapter) Info() logg.LevelLogger {
   233  	return l.infol
   234  }
   235  
   236  func (l *logAdapter) InfoCommand(command string) logg.LevelLogger {
   237  	return l.infol.WithField(FieldNameCmd, command)
   238  }
   239  
   240  func (l *logAdapter) Infof(format string, v ...any) {
   241  	l.infol.Logf(format, v...)
   242  }
   243  
   244  func (l *logAdapter) Infoln(v ...any) {
   245  	l.infol.Logf(l.sprint(v...))
   246  }
   247  
   248  func (l *logAdapter) Level() logg.Level {
   249  	return l.level
   250  }
   251  
   252  func (l *logAdapter) LoggCount(level logg.Level) int {
   253  	l.logCounters.mu.RLock()
   254  	defer l.logCounters.mu.RUnlock()
   255  	return l.logCounters.counters[level]
   256  }
   257  
   258  func (l *logAdapter) Logger() logg.Logger {
   259  	return l.logger
   260  }
   261  
   262  func (l *logAdapter) Out() io.Writer {
   263  	return l.out
   264  }
   265  
   266  // PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
   267  // if considerable time is spent.
   268  func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
   269  	elapsed := time.Since(start)
   270  	milli := int(1000 * elapsed.Seconds())
   271  	if milli < 500 {
   272  		return
   273  	}
   274  	l.Printf("%s in %v ms", name, milli)
   275  }
   276  
   277  func (l *logAdapter) Printf(format string, v ...any) {
   278  	// Add trailing newline if not present.
   279  	if !strings.HasSuffix(format, "\n") {
   280  		format += "\n"
   281  	}
   282  	fmt.Fprintf(l.out, format, v...)
   283  }
   284  
   285  func (l *logAdapter) Println(v ...any) {
   286  	fmt.Fprintln(l.out, v...)
   287  }
   288  
   289  func (l *logAdapter) Reset() {
   290  	l.reset()
   291  }
   292  
   293  func (l *logAdapter) Warn() logg.LevelLogger {
   294  	return l.warnl
   295  }
   296  
   297  func (l *logAdapter) Warnf(format string, v ...any) {
   298  	l.warnl.Logf(format, v...)
   299  }
   300  
   301  func (l *logAdapter) WarnCommand(command string) logg.LevelLogger {
   302  	return l.warnl.WithField(FieldNameCmd, command)
   303  }
   304  
   305  func (l *logAdapter) Warnln(v ...any) {
   306  	l.warnl.Logf(l.sprint(v...))
   307  }
   308  
   309  func (l *logAdapter) Error() logg.LevelLogger {
   310  	return l.errorl
   311  }
   312  
   313  func (l *logAdapter) Errorf(format string, v ...any) {
   314  	l.errorl.Logf(format, v...)
   315  }
   316  
   317  func (l *logAdapter) Errorln(v ...any) {
   318  	l.errorl.Logf(l.sprint(v...))
   319  }
   320  
   321  func (l *logAdapter) Errors() string {
   322  	return l.errors.String()
   323  }
   324  
   325  func (l *logAdapter) Erroridf(id, format string, v ...any) {
   326  	format += l.idfInfoStatement("error", id, format)
   327  	l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
   328  }
   329  
   330  func (l *logAdapter) Warnidf(id, format string, v ...any) {
   331  	format += l.idfInfoStatement("warning", id, format)
   332  	l.warnl.WithField(FieldNameStatementID, id).Logf(format, v...)
   333  }
   334  
   335  func (l *logAdapter) idfInfoStatement(what, id, format string) string {
   336  	return fmt.Sprintf("\nYou can suppress this %s by adding the following to your site configuration:\nignoreLogs = ['%s']", what, id)
   337  }
   338  
   339  func (l *logAdapter) Trace(s logg.StringFunc) {
   340  	l.tracel.Log(s)
   341  }
   342  
   343  func (l *logAdapter) sprint(v ...any) string {
   344  	return strings.TrimRight(fmt.Sprintln(v...), "\n")
   345  }
   346  
   347  func (l *logAdapter) Deprecatef(fail bool, format string, v ...any) {
   348  	format = "DEPRECATED: " + format
   349  	if fail {
   350  		l.errorl.Logf(format, v...)
   351  	} else {
   352  		l.warnl.Logf(format, v...)
   353  	}
   354  }
   355  
   356  type logWriter struct {
   357  	l logg.LevelLogger
   358  }
   359  
   360  func (w logWriter) Write(p []byte) (n int, err error) {
   361  	w.l.Log(logg.String(string(p)))
   362  	return len(p), nil
   363  }
   364  
   365  func TimeTrackf(l logg.LevelLogger, start time.Time, fields logg.Fields, format string, a ...any) {
   366  	elapsed := time.Since(start)
   367  	if fields != nil {
   368  		l = l.WithFields(fields)
   369  	}
   370  	l.WithField("duration", elapsed).Logf(format, a...)
   371  }
   372  
   373  func TimeTrackfn(fn func() (logg.LevelLogger, error)) error {
   374  	start := time.Now()
   375  	l, err := fn()
   376  	elapsed := time.Since(start)
   377  	l.WithField("duration", elapsed).Logf("")
   378  	return err
   379  }