github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/common/loggers/loggers.go (about)

     1  // Copyright 2020 The Hugo Authors. All rights reserved.
     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  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package loggers
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"io"
    20  	"log"
    21  	"os"
    22  	"regexp"
    23  	"runtime"
    24  	"time"
    25  
    26  	"github.com/gohugoio/hugo/common/terminal"
    27  
    28  	jww "github.com/spf13/jwalterweatherman"
    29  )
    30  
    31  var (
    32  	// Counts ERROR logs to the global jww logger.
    33  	GlobalErrorCounter *jww.Counter
    34  	PanicOnWarning     bool
    35  )
    36  
    37  func init() {
    38  	GlobalErrorCounter = &jww.Counter{}
    39  	jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError))
    40  }
    41  
    42  func LoggerToWriterWithPrefix(logger *log.Logger, prefix string) io.Writer {
    43  	return prefixWriter{
    44  		logger: logger,
    45  		prefix: prefix,
    46  	}
    47  }
    48  
    49  type prefixWriter struct {
    50  	logger *log.Logger
    51  	prefix string
    52  }
    53  
    54  func (w prefixWriter) Write(p []byte) (n int, err error) {
    55  	w.logger.Printf("%s: %s", w.prefix, p)
    56  	return len(p), nil
    57  }
    58  
    59  type Logger interface {
    60  	Printf(format string, v ...any)
    61  	Println(v ...any)
    62  	PrintTimerIfDelayed(start time.Time, name string)
    63  	Debug() *log.Logger
    64  	Debugf(format string, v ...any)
    65  	Debugln(v ...any)
    66  	Info() *log.Logger
    67  	Infof(format string, v ...any)
    68  	Infoln(v ...any)
    69  	Warn() *log.Logger
    70  	Warnf(format string, v ...any)
    71  	Warnln(v ...any)
    72  	Error() *log.Logger
    73  	Errorf(format string, v ...any)
    74  	Errorln(v ...any)
    75  	Errors() string
    76  
    77  	Out() io.Writer
    78  
    79  	Reset()
    80  
    81  	// Used in tests.
    82  	LogCounters() *LogCounters
    83  }
    84  
    85  type LogCounters struct {
    86  	ErrorCounter *jww.Counter
    87  	WarnCounter  *jww.Counter
    88  }
    89  
    90  type logger struct {
    91  	*jww.Notepad
    92  
    93  	// The writer that represents stdout.
    94  	// Will be io.Discard when in quiet mode.
    95  	out io.Writer
    96  
    97  	logCounters *LogCounters
    98  
    99  	// This is only set in server mode.
   100  	errors *bytes.Buffer
   101  }
   102  
   103  func (l *logger) Printf(format string, v ...any) {
   104  	l.FEEDBACK.Printf(format, v...)
   105  }
   106  
   107  func (l *logger) Println(v ...any) {
   108  	l.FEEDBACK.Println(v...)
   109  }
   110  
   111  func (l *logger) Debug() *log.Logger {
   112  	return l.DEBUG
   113  }
   114  
   115  func (l *logger) Debugf(format string, v ...any) {
   116  	l.DEBUG.Printf(format, v...)
   117  }
   118  
   119  func (l *logger) Debugln(v ...any) {
   120  	l.DEBUG.Println(v...)
   121  }
   122  
   123  func (l *logger) Infof(format string, v ...any) {
   124  	l.INFO.Printf(format, v...)
   125  }
   126  
   127  func (l *logger) Infoln(v ...any) {
   128  	l.INFO.Println(v...)
   129  }
   130  
   131  func (l *logger) Info() *log.Logger {
   132  	return l.INFO
   133  }
   134  
   135  const panicOnWarningMessage = "Warning trapped. Remove the --panicOnWarning flag to continue."
   136  
   137  func (l *logger) Warnf(format string, v ...any) {
   138  	l.WARN.Printf(format, v...)
   139  	if PanicOnWarning {
   140  		panic(panicOnWarningMessage)
   141  	}
   142  }
   143  
   144  func (l *logger) Warnln(v ...any) {
   145  	l.WARN.Println(v...)
   146  	if PanicOnWarning {
   147  		panic(panicOnWarningMessage)
   148  	}
   149  }
   150  
   151  func (l *logger) Warn() *log.Logger {
   152  	return l.WARN
   153  }
   154  
   155  func (l *logger) Errorf(format string, v ...any) {
   156  	l.ERROR.Printf(format, v...)
   157  }
   158  
   159  func (l *logger) Errorln(v ...any) {
   160  	l.ERROR.Println(v...)
   161  }
   162  
   163  func (l *logger) Error() *log.Logger {
   164  	return l.ERROR
   165  }
   166  
   167  func (l *logger) LogCounters() *LogCounters {
   168  	return l.logCounters
   169  }
   170  
   171  func (l *logger) Out() io.Writer {
   172  	return l.out
   173  }
   174  
   175  // PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
   176  // if considerable time is spent.
   177  func (l *logger) PrintTimerIfDelayed(start time.Time, name string) {
   178  	elapsed := time.Since(start)
   179  	milli := int(1000 * elapsed.Seconds())
   180  	if milli < 500 {
   181  		return
   182  	}
   183  	l.Printf("%s in %v ms", name, milli)
   184  }
   185  
   186  func (l *logger) PrintTimer(start time.Time, name string) {
   187  	elapsed := time.Since(start)
   188  	milli := int(1000 * elapsed.Seconds())
   189  	l.Printf("%s in %v ms", name, milli)
   190  }
   191  
   192  func (l *logger) Errors() string {
   193  	if l.errors == nil {
   194  		return ""
   195  	}
   196  	return ansiColorRe.ReplaceAllString(l.errors.String(), "")
   197  }
   198  
   199  // Reset resets the logger's internal state.
   200  func (l *logger) Reset() {
   201  	l.logCounters.ErrorCounter.Reset()
   202  	if l.errors != nil {
   203  		l.errors.Reset()
   204  	}
   205  }
   206  
   207  //  NewLogger creates a new Logger for the given thresholds
   208  func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) Logger {
   209  	return newLogger(stdoutThreshold, logThreshold, outHandle, logHandle, saveErrors)
   210  }
   211  
   212  // NewDebugLogger is a convenience function to create a debug logger.
   213  func NewDebugLogger() Logger {
   214  	return NewBasicLogger(jww.LevelDebug)
   215  }
   216  
   217  // NewWarningLogger is a convenience function to create a warning logger.
   218  func NewWarningLogger() Logger {
   219  	return NewBasicLogger(jww.LevelWarn)
   220  }
   221  
   222  // NewInfoLogger is a convenience function to create a info logger.
   223  func NewInfoLogger() Logger {
   224  	return NewBasicLogger(jww.LevelInfo)
   225  }
   226  
   227  // NewErrorLogger is a convenience function to create an error logger.
   228  func NewErrorLogger() Logger {
   229  	return NewBasicLogger(jww.LevelError)
   230  }
   231  
   232  // NewBasicLogger creates a new basic logger writing to Stdout.
   233  func NewBasicLogger(t jww.Threshold) Logger {
   234  	return newLogger(t, jww.LevelError, os.Stdout, io.Discard, false)
   235  }
   236  
   237  // NewBasicLoggerForWriter creates a new basic logger writing to w.
   238  func NewBasicLoggerForWriter(t jww.Threshold, w io.Writer) Logger {
   239  	return newLogger(t, jww.LevelError, w, io.Discard, false)
   240  }
   241  
   242  // RemoveANSIColours removes all ANSI colours from the given string.
   243  func RemoveANSIColours(s string) string {
   244  	return ansiColorRe.ReplaceAllString(s, "")
   245  }
   246  
   247  var (
   248  	ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m")
   249  	errorRe     = regexp.MustCompile("^(ERROR|FATAL|WARN)")
   250  )
   251  
   252  type ansiCleaner struct {
   253  	w io.Writer
   254  }
   255  
   256  func (a ansiCleaner) Write(p []byte) (n int, err error) {
   257  	return a.w.Write(ansiColorRe.ReplaceAll(p, []byte("")))
   258  }
   259  
   260  type labelColorizer struct {
   261  	w io.Writer
   262  }
   263  
   264  func (a labelColorizer) Write(p []byte) (n int, err error) {
   265  	replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string {
   266  		switch m {
   267  		case "ERROR", "FATAL":
   268  			return terminal.Error(m)
   269  		case "WARN":
   270  			return terminal.Warning(m)
   271  		default:
   272  			return m
   273  		}
   274  	})
   275  	// io.MultiWriter will abort if we return a bigger write count than input
   276  	// bytes, so we lie a little.
   277  	_, err = a.w.Write([]byte(replaced))
   278  	return len(p), err
   279  }
   280  
   281  // InitGlobalLogger initializes the global logger, used in some rare cases.
   282  func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) {
   283  	outHandle, logHandle = getLogWriters(outHandle, logHandle)
   284  
   285  	jww.SetStdoutOutput(outHandle)
   286  	jww.SetLogOutput(logHandle)
   287  	jww.SetLogThreshold(logThreshold)
   288  	jww.SetStdoutThreshold(stdoutThreshold)
   289  }
   290  
   291  func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
   292  	isTerm := terminal.PrintANSIColors(os.Stdout)
   293  	if logHandle != io.Discard && isTerm {
   294  		// Remove any Ansi coloring from log output
   295  		logHandle = ansiCleaner{w: logHandle}
   296  	}
   297  
   298  	if isTerm {
   299  		outHandle = labelColorizer{w: outHandle}
   300  	}
   301  
   302  	return outHandle, logHandle
   303  }
   304  
   305  type fatalLogWriter int
   306  
   307  func (s fatalLogWriter) Write(p []byte) (n int, err error) {
   308  	trace := make([]byte, 1500)
   309  	runtime.Stack(trace, true)
   310  	fmt.Printf("\n===========\n\n%s\n", trace)
   311  	os.Exit(-1)
   312  
   313  	return 0, nil
   314  }
   315  
   316  var fatalLogListener = func(t jww.Threshold) io.Writer {
   317  	if t != jww.LevelError {
   318  		// Only interested in ERROR
   319  		return nil
   320  	}
   321  
   322  	return new(fatalLogWriter)
   323  }
   324  
   325  func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *logger {
   326  	errorCounter := &jww.Counter{}
   327  	warnCounter := &jww.Counter{}
   328  	outHandle, logHandle = getLogWriters(outHandle, logHandle)
   329  
   330  	listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)}
   331  	var errorBuff *bytes.Buffer
   332  	if saveErrors {
   333  		errorBuff = new(bytes.Buffer)
   334  		errorCapture := func(t jww.Threshold) io.Writer {
   335  			if t != jww.LevelError {
   336  				// Only interested in ERROR
   337  				return nil
   338  			}
   339  			return errorBuff
   340  		}
   341  
   342  		listeners = append(listeners, errorCapture)
   343  	}
   344  
   345  	return &logger{
   346  		Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
   347  		out:     outHandle,
   348  		logCounters: &LogCounters{
   349  			ErrorCounter: errorCounter,
   350  			WarnCounter:  warnCounter,
   351  		},
   352  		errors: errorBuff,
   353  	}
   354  }