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