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 }