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