hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

loggers.go (8456B)

    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 ...any)
   62 	Println(v ...any)
   63 	PrintTimerIfDelayed(start time.Time, name string)
   64 	Debug() *log.Logger
   65 	Debugf(format string, v ...any)
   66 	Debugln(v ...any)
   67 	Info() *log.Logger
   68 	Infof(format string, v ...any)
   69 	Infoln(v ...any)
   70 	Warn() *log.Logger
   71 	Warnf(format string, v ...any)
   72 	Warnln(v ...any)
   73 	Error() *log.Logger
   74 	Errorf(format string, v ...any)
   75 	Errorln(v ...any)
   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 ...any) {
  105 	l.FEEDBACK.Printf(format, v...)
  106 }
  107 
  108 func (l *logger) Println(v ...any) {
  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 ...any) {
  117 	l.DEBUG.Printf(format, v...)
  118 }
  119 
  120 func (l *logger) Debugln(v ...any) {
  121 	l.DEBUG.Println(v...)
  122 }
  123 
  124 func (l *logger) Infof(format string, v ...any) {
  125 	l.INFO.Printf(format, v...)
  126 }
  127 
  128 func (l *logger) Infoln(v ...any) {
  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 ...any) {
  139 	l.WARN.Printf(format, v...)
  140 	if PanicOnWarning {
  141 		panic(panicOnWarningMessage)
  142 	}
  143 }
  144 
  145 func (l *logger) Warnln(v ...any) {
  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 ...any) {
  157 	l.ERROR.Printf(format, v...)
  158 }
  159 
  160 func (l *logger) Errorln(v ...any) {
  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 // RemoveANSIColours removes all ANSI colours from the given string.
  244 func RemoveANSIColours(s string) string {
  245 	return ansiColorRe.ReplaceAllString(s, "")
  246 }
  247 
  248 var (
  249 	ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m")
  250 	errorRe     = regexp.MustCompile("^(ERROR|FATAL|WARN)")
  251 )
  252 
  253 type ansiCleaner struct {
  254 	w io.Writer
  255 }
  256 
  257 func (a ansiCleaner) Write(p []byte) (n int, err error) {
  258 	return a.w.Write(ansiColorRe.ReplaceAll(p, []byte("")))
  259 }
  260 
  261 type labelColorizer struct {
  262 	w io.Writer
  263 }
  264 
  265 func (a labelColorizer) Write(p []byte) (n int, err error) {
  266 	replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string {
  267 		switch m {
  268 		case "ERROR", "FATAL":
  269 			return terminal.Error(m)
  270 		case "WARN":
  271 			return terminal.Warning(m)
  272 		default:
  273 			return m
  274 		}
  275 	})
  276 	// io.MultiWriter will abort if we return a bigger write count than input
  277 	// bytes, so we lie a little.
  278 	_, err = a.w.Write([]byte(replaced))
  279 	return len(p), err
  280 }
  281 
  282 // InitGlobalLogger initializes the global logger, used in some rare cases.
  283 func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) {
  284 	outHandle, logHandle = getLogWriters(outHandle, logHandle)
  285 
  286 	jww.SetStdoutOutput(outHandle)
  287 	jww.SetLogOutput(logHandle)
  288 	jww.SetLogThreshold(logThreshold)
  289 	jww.SetStdoutThreshold(stdoutThreshold)
  290 }
  291 
  292 func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
  293 	isTerm := terminal.PrintANSIColors(os.Stdout)
  294 	if logHandle != ioutil.Discard && isTerm {
  295 		// Remove any Ansi coloring from log output
  296 		logHandle = ansiCleaner{w: logHandle}
  297 	}
  298 
  299 	if isTerm {
  300 		outHandle = labelColorizer{w: outHandle}
  301 	}
  302 
  303 	return outHandle, logHandle
  304 }
  305 
  306 type fatalLogWriter int
  307 
  308 func (s fatalLogWriter) Write(p []byte) (n int, err error) {
  309 	trace := make([]byte, 1500)
  310 	runtime.Stack(trace, true)
  311 	fmt.Printf("\n===========\n\n%s\n", trace)
  312 	os.Exit(-1)
  313 
  314 	return 0, nil
  315 }
  316 
  317 var fatalLogListener = func(t jww.Threshold) io.Writer {
  318 	if t != jww.LevelError {
  319 		// Only interested in ERROR
  320 		return nil
  321 	}
  322 
  323 	return new(fatalLogWriter)
  324 }
  325 
  326 func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *logger {
  327 	errorCounter := &jww.Counter{}
  328 	warnCounter := &jww.Counter{}
  329 	outHandle, logHandle = getLogWriters(outHandle, logHandle)
  330 
  331 	listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)}
  332 	var errorBuff *bytes.Buffer
  333 	if saveErrors {
  334 		errorBuff = new(bytes.Buffer)
  335 		errorCapture := func(t jww.Threshold) io.Writer {
  336 			if t != jww.LevelError {
  337 				// Only interested in ERROR
  338 				return nil
  339 			}
  340 			return errorBuff
  341 		}
  342 
  343 		listeners = append(listeners, errorCapture)
  344 	}
  345 
  346 	return &logger{
  347 		Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
  348 		out:     outHandle,
  349 		logCounters: &LogCounters{
  350 			ErrorCounter: errorCounter,
  351 			WarnCounter:  warnCounter,
  352 		},
  353 		errors: errorBuff,
  354 	}
  355 }